Merge "Add additional compute flavor operations"

This commit is contained in:
Zuul
2020-09-22 16:42:20 +00:00
committed by Gerrit Code Review
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

@@ -59,7 +59,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.
@@ -70,8 +73,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
@@ -100,7 +106,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
@@ -110,22 +116,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

@@ -33,12 +33,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)
@@ -51,18 +47,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.