Merge "Add additional compute flavor operations"
This commit is contained in:
@@ -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
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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.
|
Reference in New Issue
Block a user