Add additional compute flavor operations

In order to proceed with switching flavor in OSC from novaclient/self
api onto SDK few new methods need to be supported.
Those include flavor access and set of extra_specs related operations.
While extra_specs in terms of SDK should be a separate resource, it is
not easy to implement it this way. On the other hand all those
operations are definive subset of flavor related operations.

In addition also merge FlavorDetail into Flavor class

Change-Id: Ia4f60acce5e0e5665e2ba704fe4cd0ec81437eb5
This commit is contained in:
Artem Goncharov
2020-09-05 14:13:40 +02:00
parent 4b7a108391
commit 5805b461eb
7 changed files with 652 additions and 40 deletions

View File

@@ -66,7 +66,12 @@ Flavor Operations
.. autoclass:: openstack.compute.v2._proxy.Proxy
:noindex:
:members: create_flavor, delete_flavor, get_flavor, find_flavor, flavors
:members: create_flavor, delete_flavor, get_flavor, find_flavor, flavors,
flavor_add_tenant_access, flavor_remove_tenant_access,
get_flavor_access, fetch_flavor_extra_specs,
create_flavor_extra_specs, get_flavor_extra_specs_property,
update_flavor_extra_specs_property,
delete_flavor_extra_specs_property
Service Operations
^^^^^^^^^^^^^^^^^^

View File

@@ -58,7 +58,10 @@ class Proxy(proxy.Proxy):
"""
return self._list(extension.Extension)
def find_flavor(self, name_or_id, ignore_missing=True):
# ========== Flavors ==========
def find_flavor(self, name_or_id, ignore_missing=True,
get_extra_specs=False):
"""Find a single flavor
:param name_or_id: The name or ID of a flavor.
@@ -69,8 +72,11 @@ class Proxy(proxy.Proxy):
attempting to find a nonexistent resource.
:returns: One :class:`~openstack.compute.v2.flavor.Flavor` or None
"""
return self._find(_flavor.Flavor, name_or_id,
ignore_missing=ignore_missing)
flavor = self._find(_flavor.Flavor, name_or_id,
ignore_missing=ignore_missing)
if flavor and get_extra_specs and not flavor.extra_specs:
flavor = flavor.fetch_extra_specs(self)
return flavor
def create_flavor(self, **attrs):
"""Create a new flavor from attributes
@@ -99,7 +105,7 @@ class Proxy(proxy.Proxy):
"""
self._delete(_flavor.Flavor, flavor, ignore_missing=ignore_missing)
def get_flavor(self, flavor):
def get_flavor(self, flavor, get_extra_specs=False):
"""Get a single flavor
:param flavor: The value can be the ID of a flavor or a
@@ -109,22 +115,121 @@ class Proxy(proxy.Proxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
"""
return self._get(_flavor.Flavor, flavor)
flavor = self._get(_flavor.Flavor, flavor)
if get_extra_specs and not flavor.extra_specs:
flavor = flavor.fetch_extra_specs(self)
return flavor
def flavors(self, details=True, **query):
"""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``*
:class:`~openstack.compute.v2.flavor.Flavor` objects,
with additional attributes filled.
:param kwargs query: Optional query parameters to be sent to limit
the flavors being returned.
the flavors being returned.
:returns: A generator of flavor objects
"""
flv = _flavor.FlavorDetail if details else _flavor.Flavor
return self._list(flv, **query)
base_path = '/flavors/detail' if details else '/flavors'
return self._list(_flavor.Flavor, base_path=base_path, **query)
def flavor_add_tenant_access(self, flavor, tenant):
"""Adds tenant/project access to flavor.
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:param str tenant: The UUID of the tenant.
:returns: One :class:`~openstack.compute.v2.flavor.Flavor`
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.add_tenant_access(self, tenant)
def flavor_remove_tenant_access(self, flavor, tenant):
"""Removes tenant/project access to flavor.
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:param str tenant: The UUID of the tenant.
:returns: One :class:`~openstack.compute.v2.flavor.Flavor`
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.remove_tenant_access(self, tenant)
def get_flavor_access(self, flavor):
"""Lists tenants who have access to private flavor
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:returns: List of dicts with flavor_id and tenant_id attributes.
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.get_access(self)
def fetch_flavor_extra_specs(self, flavor):
"""Lists Extra Specs of a flavor
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:returns: One :class:`~openstack.compute.v2.flavor.Flavor`
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.fetch_extra_specs(self)
def create_flavor_extra_specs(self, flavor, extra_specs):
"""Lists Extra Specs of a flavor
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:param dict extra_specs: dict of extra specs
:returns: One :class:`~openstack.compute.v2.flavor.Flavor`
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.create_extra_specs(self, specs=extra_specs)
def get_flavor_extra_specs_property(self, flavor, prop):
"""Get specific Extra Spec property of a flavor
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:param str prop: Property name.
:returns: String value of the requested property.
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.get_extra_specs_property(self, prop)
def update_flavor_extra_specs_property(self, flavor, prop, val):
"""Update specific Extra Spec property of a flavor
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:param str prop: Property name.
:param str val: Property value.
:returns: String value of the requested property.
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.update_extra_specs_property(self, prop, val)
def delete_flavor_extra_specs_property(self, flavor, prop):
"""Delete specific Extra Spec property of a flavor
:param flavor: Either the ID of a flavor or a
:class:`~openstack.compute.v2.flavor.Flavor` instance.
:param str prop: Property name.
:returns: None
"""
flavor = self._get_resource(_flavor.Flavor, flavor)
return flavor.delete_extra_specs_property(self, prop)
# ========== Aggregates ==========
def aggregates(self, **query):
"""Return a generator of aggregate

View File

@@ -10,7 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack import exceptions
from openstack import resource
from openstack import utils
class Flavor(resource.Resource):
@@ -62,12 +64,117 @@ class Flavor(resource.Resource):
#: A dictionary of the flavor's extra-specs key-and-value pairs.
extra_specs = resource.Body('extra_specs', type=dict)
@classmethod
def list(cls, session, paginated=True, base_path='/flavors/detail',
allow_unknown_params=False, **params):
# Find will invoke list when name was passed. Since we want to return
# flavor with details (same as direct get) we need to swap default here
# and list with "/flavors" if no details explicitely requested
if 'is_public' not in params or params['is_public'] is None:
# is_public is ternary - None means give all flavors.
# Force it to string to avoid requests skipping it.
params['is_public'] = 'None'
return super(Flavor, cls).list(
session, paginated=paginated,
base_path=base_path,
allow_unknown_params=allow_unknown_params,
**params)
class FlavorDetail(Flavor):
base_path = '/flavors/detail'
def _action(self, session, body, microversion=None):
"""Preform flavor actions given the message body."""
url = utils.urljoin(Flavor.base_path, self.id, 'action')
headers = {'Accept': ''}
attrs = {}
if microversion:
# Do not reset microversion if it is set on a session level
attrs['microversion'] = microversion
response = session.post(
url, json=body, headers=headers, **attrs)
exceptions.raise_from_response(response)
return response
allow_create = False
allow_fetch = False
allow_commit = False
allow_delete = False
allow_list = True
def add_tenant_access(self, session, tenant):
"""Adds flavor access to a tenant and flavor."""
body = {'addTenantAccess': {'tenant': tenant}}
self._action(session, body)
def remove_tenant_access(self, session, tenant):
"""Removes flavor access to a tenant and flavor."""
body = {'removeTenantAccess': {'tenant': tenant}}
self._action(session, body)
def get_access(self, session):
"""Lists tenants who have access to a private flavor and adds private
flavor access to and removes private flavor access from tenants. By
default, only administrators can manage private flavor access. A
private flavor has is_public set to false while a public flavor has
is_public set to true.
:return: List of dicts with flavor_id and tenant_id attributes
"""
url = utils.urljoin(Flavor.base_path, self.id, 'os-flavor-access')
response = session.get(url)
exceptions.raise_from_response(response)
return response.json().get('flavor_access', [])
def fetch_extra_specs(self, session):
"""Fetch extra_specs of the flavor
Starting with 2.61 extra_specs are returned with the flavor details,
before that a separate call is required
"""
url = utils.urljoin(Flavor.base_path, self.id, 'os-extra_specs')
microversion = self._get_microversion_for(session, 'fetch')
response = session.get(url, microversion=microversion)
exceptions.raise_from_response(response)
specs = response.json().get('extra_specs', {})
self._update(extra_specs=specs)
return self
def create_extra_specs(self, session, specs):
"""Creates extra specs for a flavor"""
url = utils.urljoin(Flavor.base_path, self.id, 'os-extra_specs')
microversion = self._get_microversion_for(session, 'create')
response = session.post(
url,
json={'extra_specs': specs},
microversion=microversion)
exceptions.raise_from_response(response)
specs = response.json().get('extra_specs', {})
self._update(extra_specs=specs)
return self
def get_extra_specs_property(self, session, prop):
"""Get individual extra_spec property"""
url = utils.urljoin(Flavor.base_path, self.id,
'os-extra_specs', prop)
microversion = self._get_microversion_for(session, 'fetch')
response = session.get(url, microversion=microversion)
exceptions.raise_from_response(response)
val = response.json().get(prop)
return val
def update_extra_specs_property(self, session, prop, val):
"""Update An Extra Spec For A Flavor"""
url = utils.urljoin(Flavor.base_path, self.id,
'os-extra_specs', prop)
microversion = self._get_microversion_for(session, 'commit')
response = session.put(
url,
json={prop: val},
microversion=microversion)
exceptions.raise_from_response(response)
val = response.json().get(prop)
return val
def delete_extra_specs_property(self, session, prop):
"""Delete An Extra Spec For A Flavor"""
url = utils.urljoin(Flavor.base_path, self.id,
'os-extra_specs', prop)
microversion = self._get_microversion_for(session, 'delete')
response = session.delete(
url,
microversion=microversion)
exceptions.raise_from_response(response)
FlavorDetail = Flavor

View File

@@ -9,7 +9,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from openstack import exceptions
from openstack.tests.functional import base
@@ -19,7 +19,7 @@ class TestFlavor(base.BaseFunctionalTest):
def setUp(self):
super(TestFlavor, self).setUp()
self.new_item_name = self.getUniqueString('flavor')
self.one_flavor = list(self.conn.compute.flavors())[0]
def test_flavors(self):
@@ -50,3 +50,96 @@ class TestFlavor(base.BaseFunctionalTest):
self.assertRaises(exceptions.ResourceNotFound,
self.conn.compute.find_flavor,
"not a flavor", ignore_missing=False)
def test_list_flavors(self):
pub_flavor_name = self.new_item_name + '_public'
priv_flavor_name = self.new_item_name + '_private'
public_kwargs = dict(
name=pub_flavor_name, ram=1024, vcpus=2, disk=10, is_public=True
)
private_kwargs = dict(
name=priv_flavor_name, ram=1024, vcpus=2, disk=10, is_public=False
)
# Create a public and private flavor. We expect both to be listed
# for an operator.
self.operator_cloud.compute.create_flavor(**public_kwargs)
self.operator_cloud.compute.create_flavor(**private_kwargs)
flavors = self.operator_cloud.compute.flavors()
# Flavor list will include the standard devstack flavors. We just want
# to make sure both of the flavors we just created are present.
found = []
for f in flavors:
# extra_specs should be added within list_flavors()
self.assertIn('extra_specs', f)
if f['name'] in (pub_flavor_name, priv_flavor_name):
found.append(f)
self.assertEqual(2, len(found))
def test_flavor_access(self):
flavor_name = uuid.uuid4().hex
flv = self.operator_cloud.compute.create_flavor(
is_public=False,
name=flavor_name,
ram=128,
vcpus=1,
disk=0)
self.addCleanup(self.conn.compute.delete_flavor, flv.id)
# Validate the 'demo' user cannot see the new flavor
flv_cmp = self.user_cloud.compute.find_flavor(flavor_name)
self.assertIsNone(flv_cmp)
# Validate we can see the new flavor ourselves
flv_cmp = self.operator_cloud.compute.find_flavor(flavor_name)
self.assertIsNotNone(flv_cmp)
self.assertEqual(flavor_name, flv_cmp.name)
project = self.operator_cloud.get_project('demo')
self.assertIsNotNone(project)
# Now give 'demo' access
self.operator_cloud.compute.flavor_add_tenant_access(
flv.id, project['id'])
# Now see if the 'demo' user has access to it
flv_cmp = self.user_cloud.compute.find_flavor(
flavor_name)
self.assertIsNotNone(flv_cmp)
# Now remove 'demo' access and check we can't find it
self.operator_cloud.compute.flavor_remove_tenant_access(
flv.id, project['id'])
flv_cmp = self.user_cloud.compute.find_flavor(
flavor_name)
self.assertIsNone(flv_cmp)
def test_extra_props_calls(self):
flavor_name = uuid.uuid4().hex
flv = self.conn.compute.create_flavor(
is_public=False,
name=flavor_name,
ram=128,
vcpus=1,
disk=0)
self.addCleanup(self.conn.compute.delete_flavor, flv.id)
# Create extra_specs
specs = {
'a': 'b'
}
self.conn.compute.create_flavor_extra_specs(flv, extra_specs=specs)
# verify specs
flv_cmp = self.conn.compute.fetch_flavor_extra_specs(flv)
self.assertDictEqual(specs, flv_cmp.extra_specs)
# update
self.conn.compute.update_flavor_extra_specs_property(flv, 'c', 'd')
val_cmp = self.conn.compute.get_flavor_extra_specs_property(flv, 'c')
# fetch single prop
self.assertEqual('d', val_cmp)
# drop new prop
self.conn.compute.delete_flavor_extra_specs_property(flv, 'c')
# re-fetch and ensure prev state
flv_cmp = self.conn.compute.fetch_flavor_extra_specs(flv)
self.assertDictEqual(specs, flv_cmp.extra_specs)

View File

@@ -9,6 +9,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
from keystoneauth1 import adapter
from openstack.tests.unit import base
@@ -33,6 +36,12 @@ BASIC_EXAMPLE = {
class TestFlavor(base.TestCase):
def setUp(self):
super(TestFlavor, self).setUp()
self.sess = mock.Mock(spec=adapter.Adapter)
self.sess.default_microversion = 1
self.sess._get_connection = mock.Mock(return_value=self.cloud)
def test_basic(self):
sot = flavor.Flavor()
self.assertEqual('flavor', sot.resource_key)
@@ -71,13 +80,160 @@ class TestFlavor(base.TestCase):
sot.is_disabled)
self.assertEqual(BASIC_EXAMPLE['rxtx_factor'], sot.rxtx_factor)
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.assertFalse(sot.allow_create)
self.assertFalse(sot.allow_fetch)
self.assertFalse(sot.allow_commit)
self.assertFalse(sot.allow_delete)
self.assertTrue(sot.allow_list)
def test_add_tenant_access(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
resp = mock.Mock()
resp.body = None
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.post = mock.Mock(return_value=resp)
sot.add_tenant_access(self.sess, 'fake_tenant')
self.sess.post.assert_called_with(
'flavors/IDENTIFIER/action',
json={
'addTenantAccess': {
'tenant': 'fake_tenant'}},
headers={'Accept': ''}
)
def test_remove_tenant_access(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
resp = mock.Mock()
resp.body = None
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.post = mock.Mock(return_value=resp)
sot.remove_tenant_access(self.sess, 'fake_tenant')
self.sess.post.assert_called_with(
'flavors/IDENTIFIER/action',
json={
'removeTenantAccess': {
'tenant': 'fake_tenant'}},
headers={'Accept': ''}
)
def test_get_flavor_access(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
resp = mock.Mock()
resp.body = {'flavor_access': [
{'flavor_id': 'fake_flavor',
'tenant_id': 'fake_tenant'}
]}
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.get = mock.Mock(return_value=resp)
rsp = sot.get_access(self.sess)
self.sess.get.assert_called_with(
'flavors/IDENTIFIER/os-flavor-access',
)
self.assertEqual(resp.body['flavor_access'], rsp)
def test_fetch_extra_specs(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
resp = mock.Mock()
resp.body = {
'extra_specs':
{'a': 'b',
'c': 'd'}
}
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.get = mock.Mock(return_value=resp)
rsp = sot.fetch_extra_specs(self.sess)
self.sess.get.assert_called_with(
'flavors/IDENTIFIER/os-extra_specs',
microversion=self.sess.default_microversion
)
self.assertEqual(resp.body['extra_specs'], rsp.extra_specs)
self.assertIsInstance(rsp, flavor.Flavor)
def test_create_extra_specs(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
specs = {
'a': 'b',
'c': 'd'
}
resp = mock.Mock()
resp.body = {
'extra_specs': specs
}
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.post = mock.Mock(return_value=resp)
rsp = sot.create_extra_specs(self.sess, specs)
self.sess.post.assert_called_with(
'flavors/IDENTIFIER/os-extra_specs',
json={'extra_specs': specs},
microversion=self.sess.default_microversion
)
self.assertEqual(resp.body['extra_specs'], rsp.extra_specs)
self.assertIsInstance(rsp, flavor.Flavor)
def test_get_extra_specs_property(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
resp = mock.Mock()
resp.body = {
'a': 'b'
}
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.get = mock.Mock(return_value=resp)
rsp = sot.get_extra_specs_property(self.sess, 'a')
self.sess.get.assert_called_with(
'flavors/IDENTIFIER/os-extra_specs/a',
microversion=self.sess.default_microversion
)
self.assertEqual('b', rsp)
def test_update_extra_specs_property(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
resp = mock.Mock()
resp.body = {
'a': 'b'
}
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.put = mock.Mock(return_value=resp)
rsp = sot.update_extra_specs_property(self.sess, 'a', 'b')
self.sess.put.assert_called_with(
'flavors/IDENTIFIER/os-extra_specs/a',
json={'a': 'b'},
microversion=self.sess.default_microversion
)
self.assertEqual('b', rsp)
def test_delete_extra_specs_property(self):
sot = flavor.Flavor(**BASIC_EXAMPLE)
resp = mock.Mock()
resp.body = None
resp.json = mock.Mock(return_value=resp.body)
resp.status_code = 200
self.sess.delete = mock.Mock(return_value=resp)
rsp = sot.delete_extra_specs_property(self.sess, 'a')
self.sess.delete.assert_called_with(
'flavors/IDENTIFIER/os-extra_specs/a',
microversion=self.sess.default_microversion
)
self.assertIsNone(rsp)

View File

@@ -32,12 +32,8 @@ class TestComputeProxy(test_proxy_base.TestProxyBase):
super(TestComputeProxy, self).setUp()
self.proxy = _proxy.Proxy(self.session)
def test_extension_find(self):
self.verify_find(self.proxy.find_extension, extension.Extension)
def test_extensions(self):
self.verify_list_no_kwargs(self.proxy.extensions, extension.Extension)
class TestFlavor(TestComputeProxy):
def test_flavor_create(self):
self.verify_create(self.proxy.create_flavor, flavor.Flavor)
@@ -50,18 +46,161 @@ class TestComputeProxy(test_proxy_base.TestProxyBase):
def test_flavor_find(self):
self.verify_find(self.proxy.find_flavor, flavor.Flavor)
def test_flavor_get(self):
self.verify_get(self.proxy.get_flavor, flavor.Flavor)
def test_flavor_find_fetch_extra(self):
"""fetch extra_specs is triggered"""
with mock.patch(
'openstack.compute.v2.flavor.Flavor.fetch_extra_specs'
) as mocked:
res = flavor.Flavor()
mocked.return_value = res
self._verify2(
'openstack.proxy.Proxy._find',
self.proxy.find_flavor,
method_args=['res', True, True],
expected_result=res,
expected_args=[flavor.Flavor, 'res'],
expected_kwargs={'ignore_missing': True}
)
mocked.assert_called_once()
def test_flavor_find_skip_fetch_extra(self):
"""fetch extra_specs not triggered"""
with mock.patch(
'openstack.compute.v2.flavor.Flavor.fetch_extra_specs'
) as mocked:
res = flavor.Flavor(extra_specs={'a': 'b'})
mocked.return_value = res
self._verify2(
'openstack.proxy.Proxy._find',
self.proxy.find_flavor,
method_args=['res', True],
expected_result=res,
expected_args=[flavor.Flavor, 'res'],
expected_kwargs={'ignore_missing': True}
)
mocked.assert_not_called()
def test_flavor_get_no_extra(self):
"""fetch extra_specs not triggered"""
with mock.patch(
'openstack.compute.v2.flavor.Flavor.fetch_extra_specs'
) as mocked:
res = flavor.Flavor()
mocked.return_value = res
self._verify2(
'openstack.proxy.Proxy._get',
self.proxy.get_flavor,
method_args=['res'],
expected_result=res,
expected_args=[flavor.Flavor, 'res']
)
mocked.assert_not_called()
def test_flavor_get_fetch_extra(self):
"""fetch extra_specs is triggered"""
with mock.patch(
'openstack.compute.v2.flavor.Flavor.fetch_extra_specs'
) as mocked:
res = flavor.Flavor()
mocked.return_value = res
self._verify2(
'openstack.proxy.Proxy._get',
self.proxy.get_flavor,
method_args=['res', True],
expected_result=res,
expected_args=[flavor.Flavor, 'res']
)
mocked.assert_called_once()
def test_flavor_get_skip_fetch_extra(self):
"""fetch extra_specs not triggered"""
with mock.patch(
'openstack.compute.v2.flavor.Flavor.fetch_extra_specs'
) as mocked:
res = flavor.Flavor(extra_specs={'a': 'b'})
mocked.return_value = res
self._verify2(
'openstack.proxy.Proxy._get',
self.proxy.get_flavor,
method_args=['res', True],
expected_result=res,
expected_args=[flavor.Flavor, 'res']
)
mocked.assert_not_called()
def test_flavors_detailed(self):
self.verify_list(self.proxy.flavors, flavor.FlavorDetail,
method_kwargs={"details": True, "query": 1},
expected_kwargs={"query": 1})
expected_kwargs={"query": 1,
"base_path": "/flavors/detail"})
def test_flavors_not_detailed(self):
self.verify_list(self.proxy.flavors, flavor.Flavor,
method_kwargs={"details": False, "query": 1},
expected_kwargs={"query": 1})
expected_kwargs={"query": 1,
"base_path": "/flavors"})
def test_flavor_get_access(self):
self._verify("openstack.compute.v2.flavor.Flavor.get_access",
self.proxy.get_flavor_access,
method_args=["value"],
expected_args=[])
def test_flavor_add_tenant_access(self):
self._verify("openstack.compute.v2.flavor.Flavor.add_tenant_access",
self.proxy.flavor_add_tenant_access,
method_args=["value", "fake-tenant"],
expected_args=["fake-tenant"])
def test_flavor_remove_tenant_access(self):
self._verify("openstack.compute.v2.flavor.Flavor.remove_tenant_access",
self.proxy.flavor_remove_tenant_access,
method_args=["value", "fake-tenant"],
expected_args=["fake-tenant"])
def test_flavor_fetch_extra_specs(self):
self._verify("openstack.compute.v2.flavor.Flavor.fetch_extra_specs",
self.proxy.fetch_flavor_extra_specs,
method_args=["value"],
expected_args=[])
def test_create_flavor_extra_specs(self):
specs = {
'a': 'b'
}
self._verify("openstack.compute.v2.flavor.Flavor.create_extra_specs",
self.proxy.create_flavor_extra_specs,
method_args=["value", specs],
expected_kwargs={"specs": specs})
def test_get_flavor_extra_specs_prop(self):
self._verify(
"openstack.compute.v2.flavor.Flavor.get_extra_specs_property",
self.proxy.get_flavor_extra_specs_property,
method_args=["value", "prop"],
expected_args=["prop"])
def test_update_flavor_extra_specs_prop(self):
self._verify(
"openstack.compute.v2.flavor.Flavor.update_extra_specs_property",
self.proxy.update_flavor_extra_specs_property,
method_args=["value", "prop", "val"],
expected_args=["prop", "val"])
def test_delete_flavor_extra_specs_prop(self):
self._verify(
"openstack.compute.v2.flavor.Flavor.delete_extra_specs_property",
self.proxy.delete_flavor_extra_specs_property,
method_args=["value", "prop"],
expected_args=["prop"])
class TestCompute(TestComputeProxy):
def test_extension_find(self):
self.verify_find(self.proxy.find_extension, extension.Extension)
def test_extensions(self):
self.verify_list_no_kwargs(self.proxy.extensions, extension.Extension)
def test_image_delete(self):
self.verify_delete(self.proxy.delete_image, image.Image, False)

View File

@@ -0,0 +1,7 @@
---
features:
- |
Add additional compute flavor operations (flavor_add_tenant_access, flavor_remove_tenant_access, get_flavor_access, extra_specs fetching/updating).
other:
- |
Merge FlavorDetails into Flavor class.