Add FlavorDetail for extra information

The current Flavor can only receive the basics returned by a GET
/flavors, which is just name, id, and links. A call to /flavors/detail
gives a number of added details, some of which were already in the
Flavor resource but not set. This change follows with how a few other
basic/detail requests were done, and exposes it through the proxy with
details coming by default (since they're likely expected, and most
useful).

This change depends on list handling nonpaginated bodies.t

Change-Id: I7bebbdc2aa86c0300b86a8b8daa34888121dbcff
This commit is contained in:
Brian Curtin
2015-02-22 22:59:07 -06:00
committed by Brian Curtin
parent d76624895c
commit 9ef6dc3bdc
6 changed files with 135 additions and 45 deletions

View File

@@ -10,3 +10,12 @@ The ``Flavor`` class inherits from :class:`~openstack.resource.Resource`.
.. autoclass:: openstack.compute.v2.flavor.Flavor
:members:
The FlavorDetail Class
----------------------
The ``FlavorDetail`` class inherits from
:class:`~openstack.compute.v2.flavor.Flavor`.
.. autoclass:: openstack.compute.v2.flavor.FlavorDetail
:members:

View File

@@ -31,20 +31,30 @@ class Proxy(object):
def list_extensions(self):
return extension.Extension.list(self.session)
def find_flavor(self, name_or_id):
return flavor.Flavor.find(self.session, name_or_id)
def create_flavor(self, **data):
return flavor.Flavor(data).create(self.session)
def delete_flavor(self, **data):
flavor.Flavor(data).delete(self.session)
def find_flavor(self, name_or_id):
return flavor.Flavor.find(self.session, name_or_id)
def get_flavor(self, **data):
return flavor.Flavor(data).get(self.session)
def list_flavors(self, **params):
return flavor.Flavor.list(self.session, **params)
def list_flavors(self, details=True, **params):
"""Return a generator of flavors
:param bool details: When ``True``, returns
:class:`~openstack.compute.v2.flavor.FlavorDetail` objects,
otherwise :class:`~openstack.compute.v2.flavor.Flavor`.
*Default: ``True``*
:returns: A generator of flavor objects
"""
flv = flavor.FlavorDetail if details else flavor.Flavor
return flv.list(self.session, paginate=False, **params)
def update_flavor(self, **data):
return flavor.Flavor(data).update(self.session)

View File

@@ -28,17 +28,30 @@ class Flavor(resource.Resource):
allow_list = True
# Properties
#: Size of the disk this flavor offers. *Type: int*
disk = resource.prop('disk', type=int)
#: ``True`` if this is a publicly visible flavor. ``False`` if this is
#: a private image. *Type: bool*
is_public = resource.prop('os-flavor-access:is_public', type=bool)
#: Links pertaining to this flavor. This is a list of dictionaries,
#: each including keys ``href`` and ``rel``.
links = resource.prop('links')
#: The name of this flavor.
name = resource.prop('name')
class FlavorDetail(Flavor):
base_path = '/flavors/detail'
#: Size of the disk this flavor offers. *Type: int*
disk = resource.prop('disk', type=int)
#: ``True`` if this is a publicly visible flavor. ``False`` if this is
#: a private image. *Type: bool*
is_public = resource.prop('os-flavor-access:is_public', type=bool)
#: The amount of RAM (in MB) this flavor offers. *Type: int*
ram = resource.prop('ram', type=int)
#: The number of virtual CPUs this flavor offers. *Type: int*
vcpus = resource.prop('vcpus', type=int)
#: Size of the swap partitions.
swap = resource.prop('swap')
#: Size of the ephemeral data disk attached to this server. *Type: int*
ephemeral = resource.prop('OS-FLV-EXT-DATA:ephemeral', type=int)
#: ``True`` if this flavor is disabled, ``False`` if not.
disabled = resource.prop('OS-FLV-DISABLED:disabled')
#: The bandwidth scaling factor this flavor receives on the network.
rxtx_factor = resource.prop('rxtx_factor', type=float)

View File

@@ -15,16 +15,26 @@ import testtools
from openstack.compute.v2 import flavor
IDENTIFIER = 'IDENTIFIER'
EXAMPLE = {
'disk': 1,
BASIC_EXAMPLE = {
'id': IDENTIFIER,
'os-flavor-access:is_public': True,
'links': '4',
'name': '5',
'ram': 6,
'vcpus': 7,
}
DETAILS = {
'disk': 1,
'os-flavor-access:is_public': True,
'ram': 3,
'vcpus': 4,
'swap': 5,
'OS-FLV-EXT-DATA:ephemeral': 6,
'OS-FLV-DISABLED:disabled': False,
'rxtx_factor': 8.0
}
DETAIL_EXAMPLE = BASIC_EXAMPLE.copy()
DETAIL_EXAMPLE.update(DETAILS)
class TestFlavor(testtools.TestCase):
@@ -40,12 +50,37 @@ class TestFlavor(testtools.TestCase):
self.assertTrue(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_make_it(self):
sot = flavor.Flavor(EXAMPLE)
self.assertEqual(EXAMPLE['disk'], sot.disk)
self.assertEqual(EXAMPLE['id'], sot.id)
self.assertEqual(EXAMPLE['os-flavor-access:is_public'], sot.is_public)
self.assertEqual(EXAMPLE['links'], sot.links)
self.assertEqual(EXAMPLE['name'], sot.name)
self.assertEqual(EXAMPLE['ram'], sot.ram)
self.assertEqual(EXAMPLE['vcpus'], sot.vcpus)
def test_make_basic(self):
sot = flavor.Flavor(BASIC_EXAMPLE)
self.assertEqual(BASIC_EXAMPLE['id'], sot.id)
self.assertEqual(BASIC_EXAMPLE['links'], sot.links)
self.assertEqual(BASIC_EXAMPLE['name'], sot.name)
def test_detail(self):
sot = flavor.FlavorDetail()
self.assertEqual('flavor', sot.resource_key)
self.assertEqual('flavors', sot.resources_key)
self.assertEqual('/flavors/detail', sot.base_path)
self.assertEqual('compute', sot.service.service_type)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_retrieve)
self.assertTrue(sot.allow_update)
self.assertTrue(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_make_detail(self):
sot = flavor.FlavorDetail(DETAIL_EXAMPLE)
self.assertEqual(DETAIL_EXAMPLE['id'], sot.id)
self.assertEqual(DETAIL_EXAMPLE['links'], sot.links)
self.assertEqual(DETAIL_EXAMPLE['name'], sot.name)
self.assertEqual(DETAIL_EXAMPLE['disk'], sot.disk)
self.assertEqual(DETAIL_EXAMPLE['os-flavor-access:is_public'],
sot.is_public)
self.assertEqual(DETAIL_EXAMPLE['ram'], sot.ram)
self.assertEqual(DETAIL_EXAMPLE['vcpus'], sot.vcpus)
self.assertEqual(DETAIL_EXAMPLE['swap'], sot.swap)
self.assertEqual(DETAIL_EXAMPLE['OS-FLV-EXT-DATA:ephemeral'],
sot.ephemeral)
self.assertEqual(DETAIL_EXAMPLE['OS-FLV-DISABLED:disabled'],
sot.disabled)
self.assertEqual(DETAIL_EXAMPLE['rxtx_factor'], sot.rxtx_factor)

View File

@@ -43,9 +43,17 @@ class TestComputeProxy(test_proxy_base.TestProxyBase):
self.verify_get('openstack.compute.v2.flavor.Flavor.get',
self.proxy.get_flavor)
def test_flavor_list(self):
def test_flavor_list_basic(self):
self.verify_list('openstack.compute.v2.flavor.Flavor.list',
self.proxy.list_flavors)
self.proxy.list_flavors,
method_kwargs={"details": False},
expected_kwargs={"paginate": False})
def test_flavor_list_detail(self):
self.verify_list('openstack.compute.v2.flavor.FlavorDetail.list',
self.proxy.list_flavors,
method_kwargs={"details": True},
expected_kwargs={"paginate": False})
def test_flavor_update(self):
self.verify_update('openstack.compute.v2.flavor.Flavor.update',

View File

@@ -20,31 +20,46 @@ class TestProxyBase(base.TestCase):
super(TestProxyBase, self).setUp()
self.session = mock.MagicMock()
def _verify(self, mock_method, test_method, method_args=None,
expected=None):
def _verify(self, mock_method, test_method,
method_args=None, method_kwargs=None,
expected_args=None, expected_kwargs=None,
expected_result=None):
with mock.patch(mock_method) as mocked:
mocked.return_value = expected
if method_args is not None:
self.assertEqual(expected, test_method(method_args))
mocked.assert_called_with(self.session, method_args)
mocked.return_value = expected_result
if any([method_args, method_kwargs]):
method_args = method_args or ()
method_kwargs = method_kwargs or {}
expected_args = expected_args or ()
expected_kwargs = expected_kwargs or {}
self.assertEqual(expected_result, test_method(*method_args,
**method_kwargs))
mocked.assert_called_with(self.session,
*expected_args, **expected_kwargs)
else:
self.assertEqual(expected, test_method())
self.assertEqual(expected_result, test_method())
mocked.assert_called_with(self.session)
def verify_create(self, mock_method, test_method):
self._verify(mock_method, test_method, expected="result")
def verify_create(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, expected_result="result",
**kwargs)
def verify_delete(self, mock_method, test_method):
self._verify(mock_method, test_method)
def verify_delete(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, **kwargs)
def verify_get(self, mock_method, test_method):
self._verify(mock_method, test_method, expected="result")
def verify_get(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, expected_result="result",
**kwargs)
def verify_find(self, mock_method, test_method):
self._verify(mock_method, test_method, ["name_or_id"], "result")
def verify_find(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, method_args=["name_or_id"],
expected_args=["name_or_id"], expected_result="result",
**kwargs)
def verify_list(self, mock_method, test_method):
self._verify(mock_method, test_method, expected=["result"])
def verify_list(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, expected_result=["result"],
**kwargs)
def verify_update(self, mock_method, test_method):
self._verify(mock_method, test_method, expected="result")
def verify_update(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, expected_result="result",
**kwargs)