diff options
author | Mauro S. M. Rodrigues <maurosr@linux.vnet.ibm.com> | 2013-05-10 14:03:51 -0400 |
---|---|---|
committer | Mauro S. M. Rodrigues <maurosr@linux.vnet.ibm.com> | 2013-06-13 13:25:43 -0400 |
commit | f6838316da6f69851cba06f9b45a91a2ee04179a (patch) | |
tree | db276fc6d21756d1753906af8a6d6874edd3081a | |
parent | 90da2c677976b3e9e96bd68703a8b9d5ba347ce3 (diff) | |
download | nova-f6838316da6f69851cba06f9b45a91a2ee04179a.tar.gz nova-f6838316da6f69851cba06f9b45a91a2ee04179a.tar.xz nova-f6838316da6f69851cba06f9b45a91a2ee04179a.zip |
Port flavors core API to v3 tree
Port flavors core API (which does *not* include all extensions, like flavor
create) to the V3 API, adapting the way it's loaded to fit with the new
extension framework and, of course, its tests.
This also change the flavors requests and responses links on unit tests since
project_id is no longer present on v3 url
Partially implements blueprint v3-api-core-as-extensions
Change-Id: Ide2eb22a89be3bd9adcf5e09cbef23e824a83fc1
-rw-r--r-- | nova/api/openstack/compute/plugins/v3/flavors.py | 167 | ||||
-rw-r--r-- | nova/tests/api/openstack/compute/plugins/v3/test_flavors.py | 792 | ||||
-rw-r--r-- | setup.cfg | 1 |
3 files changed, 960 insertions, 0 deletions
diff --git a/nova/api/openstack/compute/plugins/v3/flavors.py b/nova/api/openstack/compute/plugins/v3/flavors.py new file mode 100644 index 000000000..733ff3750 --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/flavors.py @@ -0,0 +1,167 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 webob + +from nova.api.openstack import common +from nova.api.openstack.compute.views import flavors as flavors_view +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova.compute import flavors +from nova import exception +from nova.openstack.common import strutils + + +def make_flavor(elem, detailed=False): + elem.set('name') + elem.set('id') + if detailed: + elem.set('ram') + elem.set('disk') + elem.set('vcpus', xmlutil.EmptyStringSelector('vcpus')) + + xmlutil.make_links(elem, 'links') + + +flavor_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} + + +class FlavorTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('flavor', selector='flavor') + make_flavor(root, detailed=True) + return xmlutil.MasterTemplate(root, 1, nsmap=flavor_nsmap) + + +class MinimalFlavorsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('flavors') + elem = xmlutil.SubTemplateElement(root, 'flavor', selector='flavors') + make_flavor(elem) + return xmlutil.MasterTemplate(root, 1, nsmap=flavor_nsmap) + + +class FlavorsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('flavors') + elem = xmlutil.SubTemplateElement(root, 'flavor', selector='flavors') + make_flavor(elem, detailed=True) + return xmlutil.MasterTemplate(root, 1, nsmap=flavor_nsmap) + + +class FlavorsController(wsgi.Controller): + """Flavor controller for the OpenStack API.""" + + _view_builder_class = flavors_view.ViewBuilder + + @wsgi.serializers(xml=MinimalFlavorsTemplate) + def index(self, req): + """Return all flavors in brief.""" + limited_flavors = self._get_flavors(req) + return self._view_builder.index(req, limited_flavors) + + @wsgi.serializers(xml=FlavorsTemplate) + def detail(self, req): + """Return all flavors in detail.""" + limited_flavors = self._get_flavors(req) + req.cache_db_flavors(limited_flavors) + return self._view_builder.detail(req, limited_flavors) + + @wsgi.serializers(xml=FlavorTemplate) + def show(self, req, id): + """Return data about the given flavor id.""" + try: + flavor = flavors.get_flavor_by_flavor_id(id) + req.cache_db_flavor(flavor) + except exception.NotFound: + raise webob.exc.HTTPNotFound() + + return self._view_builder.show(req, flavor) + + def _parse_is_public(self, is_public): + """Parse is_public into something usable.""" + + if is_public is None: + # preserve default value of showing only public flavors + return True + elif is_public == 'none': + return None + else: + try: + return strutils.bool_from_string(is_public, strict=True) + except ValueError: + msg = _('Invalid is_public filter [%s]') % is_public + raise webob.exc.HTTPBadRequest(explanation=msg) + + def _get_flavors(self, req): + """Helper function that returns a list of flavor dicts.""" + filters = {} + + context = req.environ['nova.context'] + if context.is_admin: + # Only admin has query access to all flavor types + filters['is_public'] = self._parse_is_public( + req.params.get('is_public', None)) + else: + filters['is_public'] = True + filters['disabled'] = False + + if 'minRam' in req.params: + try: + filters['min_memory_mb'] = int(req.params['minRam']) + except ValueError: + msg = _('Invalid minRam filter [%s]') % req.params['minRam'] + raise webob.exc.HTTPBadRequest(explanation=msg) + + if 'minDisk' in req.params: + try: + filters['min_root_gb'] = int(req.params['minDisk']) + except ValueError: + msg = _('Invalid minDisk filter [%s]') % req.params['minDisk'] + raise webob.exc.HTTPBadRequest(explanation=msg) + + limited_flavors = flavors.get_all_flavors(context, filters=filters) + flavors_list = limited_flavors.values() + sorted_flavors = sorted(flavors_list, + key=lambda item: item['flavorid']) + limited_flavors = common.limited_by_marker(sorted_flavors, req) + return limited_flavors + + +class Flavors(extensions.V3APIExtensionBase): + """ Flavors Extension. """ + name = "flavors" + alias = "flavors" + namespace = "http://docs.openstack.org/compute/core/flavors/v3" + version = 1 + + def get_resources(self): + collection_actions = {'detail': 'GET'} + member_actions = {'action': 'POST'} + + resources = [ + extensions.ResourceExtension('flavors', + FlavorsController(), + member_name='flavor', + collection_actions=collection_actions, + member_actions=member_actions) + ] + return resources + + def get_controller_extensions(self): + return [] diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_flavors.py b/nova/tests/api/openstack/compute/plugins/v3/test_flavors.py new file mode 100644 index 000000000..6680460de --- /dev/null +++ b/nova/tests/api/openstack/compute/plugins/v3/test_flavors.py @@ -0,0 +1,792 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 lxml import etree +import webob + +import urlparse + +from nova.api.openstack.compute.plugins.v3 import flavors +from nova.api.openstack import xmlutil +import nova.compute.flavors +from nova import context +from nova import db +from nova import exception +from nova import test +from nova.tests.api.openstack import fakes +from nova.tests import matchers + +NS = "{http://docs.openstack.org/compute/api/v1.1}" +ATOMNS = "{http://www.w3.org/2005/Atom}" + + +FAKE_FLAVORS = { + 'flavor 1': { + "flavorid": '1', + "name": 'flavor 1', + "memory_mb": '256', + "root_gb": '10', + }, + 'flavor 2': { + "flavorid": '2', + "name": 'flavor 2', + "memory_mb": '512', + "root_gb": '20', + }, +} + + +def fake_flavor_get_by_flavor_id(flavorid): + return FAKE_FLAVORS['flavor %s' % flavorid] + + +def fake_flavor_get_all(inactive=False, filters=None): + def reject_min(db_attr, filter_attr): + return (filter_attr in filters and + int(flavor[db_attr]) < int(filters[filter_attr])) + + filters = filters or {} + output = {} + for (flavor_name, flavor) in FAKE_FLAVORS.items(): + if reject_min('memory_mb', 'min_memory_mb'): + continue + elif reject_min('root_gb', 'min_root_gb'): + continue + + output[flavor_name] = flavor + + return output + + +def empty_flavor_get_all(inactive=False, filters=None): + return {} + + +def return_flavor_not_found(flavor_id): + raise exception.InstanceTypeNotFound(instance_type_id=flavor_id) + + +class FlavorsTest(test.TestCase): + def setUp(self): + super(FlavorsTest, self).setUp() + self.flags(osapi_compute_extension=[]) + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + self.stubs.Set(nova.compute.flavors, "get_all_flavors", + fake_flavor_get_all) + self.stubs.Set(nova.compute.flavors, + "get_flavor_by_flavor_id", + fake_flavor_get_by_flavor_id) + + self.controller = flavors.FlavorsController() + + def test_get_flavor_by_invalid_id(self): + self.stubs.Set(nova.compute.flavors, + "get_flavor_by_flavor_id", + return_flavor_not_found) + req = fakes.HTTPRequestV3.blank('/flavors/asdf') + self.assertRaises(webob.exc.HTTPNotFound, + self.controller.show, req, 'asdf') + + def test_get_flavor_by_id(self): + req = fakes.HTTPRequestV3.blank('/flavors/1') + flavor = self.controller.show(req, '1') + expected = { + "flavor": { + "id": "1", + "name": "flavor 1", + "ram": "256", + "disk": "10", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + } + self.assertEqual(flavor, expected) + + def test_get_flavor_with_custom_link_prefix(self): + self.flags(osapi_compute_link_prefix='http://zoo.com:42', + osapi_glance_link_prefix='http://circus.com:34') + req = fakes.HTTPRequestV3.blank('/flavors/1') + flavor = self.controller.show(req, '1') + expected = { + "flavor": { + "id": "1", + "name": "flavor 1", + "ram": "256", + "disk": "10", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://zoo.com:42/v3/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://zoo.com:42/flavors/1", + }, + ], + }, + } + self.assertEqual(flavor, expected) + + def test_get_flavor_list(self): + req = fakes.HTTPRequestV3.blank('/flavors') + flavor = self.controller.index(req) + expected = { + "flavors": [ + { + "id": "1", + "name": "flavor 1", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } + self.assertEqual(flavor, expected) + + def test_get_flavor_list_with_marker(self): + self.maxDiff = None + req = fakes.HTTPRequestV3.blank('/flavors?limit=1&marker=1') + flavor = self.controller.index(req) + expected = { + "flavors": [ + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + 'flavors_links': [ + {'href': 'http://localhost/v3/flavors?limit=1&marker=2', + 'rel': 'next'} + ] + } + self.assertThat(flavor, matchers.DictMatches(expected)) + + def test_get_flavor_detail_with_limit(self): + req = fakes.HTTPRequestV3.blank('/flavors/detail?limit=1') + response = self.controller.index(req) + response_list = response["flavors"] + response_links = response["flavors_links"] + + expected_flavors = [ + { + "id": "1", + "name": "flavor 1", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + ] + self.assertEqual(response_list, expected_flavors) + self.assertEqual(response_links[0]['rel'], 'next') + + href_parts = urlparse.urlparse(response_links[0]['href']) + self.assertEqual('/v3/flavors', href_parts.path) + params = urlparse.parse_qs(href_parts.query) + self.assertThat({'limit': ['1'], 'marker': ['1']}, + matchers.DictMatches(params)) + + def test_get_flavor_with_limit(self): + req = fakes.HTTPRequestV3.blank('/flavors?limit=2') + response = self.controller.index(req) + response_list = response["flavors"] + response_links = response["flavors_links"] + + expected_flavors = [ + { + "id": "1", + "name": "flavor 1", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + } + ] + self.assertEqual(response_list, expected_flavors) + self.assertEqual(response_links[0]['rel'], 'next') + + href_parts = urlparse.urlparse(response_links[0]['href']) + self.assertEqual('/v3/flavors', href_parts.path) + params = urlparse.parse_qs(href_parts.query) + self.assertThat({'limit': ['2'], 'marker': ['2']}, + matchers.DictMatches(params)) + + def test_get_flavor_list_detail(self): + req = fakes.HTTPRequestV3.blank('/flavors/detail') + flavor = self.controller.detail(req) + expected = { + "flavors": [ + { + "id": "1", + "name": "flavor 1", + "ram": "256", + "disk": "10", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "ram": "512", + "disk": "20", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } + self.assertEqual(flavor, expected) + + def test_get_empty_flavor_list(self): + self.stubs.Set(nova.compute.flavors, "get_all_flavors", + empty_flavor_get_all) + + req = fakes.HTTPRequestV3.blank('/flavors') + flavors = self.controller.index(req) + expected = {'flavors': []} + self.assertEqual(flavors, expected) + + def test_get_flavor_list_filter_min_ram(self): + # Flavor lists may be filtered by minRam. + req = fakes.HTTPRequestV3.blank('/flavors?minRam=512') + flavor = self.controller.index(req) + expected = { + "flavors": [ + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } + self.assertEqual(flavor, expected) + + def test_get_flavor_list_filter_invalid_min_ram(self): + # Ensure you cannot list flavors with invalid minRam param. + req = fakes.HTTPRequestV3.blank('/flavors?minRam=NaN') + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.index, req) + + def test_get_flavor_list_filter_min_disk(self): + # Flavor lists may be filtered by minDisk. + req = fakes.HTTPRequestV3.blank('/flavors?minDisk=20') + flavor = self.controller.index(req) + expected = { + "flavors": [ + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } + self.assertEqual(flavor, expected) + + def test_get_flavor_list_filter_invalid_min_disk(self): + # Ensure you cannot list flavors with invalid minDisk param. + req = fakes.HTTPRequestV3.blank('/flavors?minDisk=NaN') + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.index, req) + + def test_get_flavor_list_detail_min_ram_and_min_disk(self): + """Tests that filtering work on flavor details and that minRam and + minDisk filters can be combined + """ + req = fakes.HTTPRequestV3.blank('/flavors/detail' + '?minRam=256&minDisk=20') + flavor = self.controller.detail(req) + expected = { + "flavors": [ + { + "id": "2", + "name": "flavor 2", + "ram": "512", + "disk": "20", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/2", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/2", + }, + ], + }, + ], + } + self.assertEqual(flavor, expected) + + +class FlavorsXMLSerializationTest(test.TestCase): + + def test_xml_declaration(self): + serializer = flavors.FlavorTemplate() + + fixture = { + "flavor": { + "id": "12", + "name": "asdf", + "ram": "256", + "disk": "10", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/12", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/12", + }, + ], + }, + } + + output = serializer.serialize(fixture) + has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>") + self.assertTrue(has_dec) + + def test_show(self): + serializer = flavors.FlavorTemplate() + + fixture = { + "flavor": { + "id": "12", + "name": "asdf", + "ram": "256", + "disk": "10", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/12", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/12", + }, + ], + }, + } + + output = serializer.serialize(fixture) + root = etree.XML(output) + xmlutil.validate_schema(root, 'flavor') + flavor_dict = fixture['flavor'] + + for key in ['name', 'id', 'ram', 'disk']: + self.assertEqual(root.get(key), str(flavor_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(flavor_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + def test_show_handles_integers(self): + serializer = flavors.FlavorTemplate() + + fixture = { + "flavor": { + "id": 12, + "name": "asdf", + "ram": 256, + "disk": 10, + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/12", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/12", + }, + ], + }, + } + + output = serializer.serialize(fixture) + root = etree.XML(output) + xmlutil.validate_schema(root, 'flavor') + flavor_dict = fixture['flavor'] + + for key in ['name', 'id', 'ram', 'disk']: + self.assertEqual(root.get(key), str(flavor_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(flavor_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + def test_detail(self): + serializer = flavors.FlavorsTemplate() + + fixture = { + "flavors": [ + { + "id": "23", + "name": "flavor 23", + "ram": "512", + "disk": "20", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/23", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/23", + }, + ], + }, + { + "id": "13", + "name": "flavor 13", + "ram": "256", + "disk": "10", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/13", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/13", + }, + ], + }, + ], + } + + output = serializer.serialize(fixture) + root = etree.XML(output) + xmlutil.validate_schema(root, 'flavors') + flavor_elems = root.findall('{0}flavor'.format(NS)) + self.assertEqual(len(flavor_elems), 2) + for i, flavor_elem in enumerate(flavor_elems): + flavor_dict = fixture['flavors'][i] + + for key in ['name', 'id', 'ram', 'disk']: + self.assertEqual(flavor_elem.get(key), str(flavor_dict[key])) + + link_nodes = flavor_elem.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(flavor_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + def test_index(self): + serializer = flavors.MinimalFlavorsTemplate() + + fixture = { + "flavors": [ + { + "id": "23", + "name": "flavor 23", + "ram": "512", + "disk": "20", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/23", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/23", + }, + ], + }, + { + "id": "13", + "name": "flavor 13", + "ram": "256", + "disk": "10", + "vcpus": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v3/flavors/13", + }, + { + "rel": "bookmark", + "href": "http://localhost/flavors/13", + }, + ], + }, + ], + } + + output = serializer.serialize(fixture) + root = etree.XML(output) + xmlutil.validate_schema(root, 'flavors_index') + flavor_elems = root.findall('{0}flavor'.format(NS)) + self.assertEqual(len(flavor_elems), 2) + for i, flavor_elem in enumerate(flavor_elems): + flavor_dict = fixture['flavors'][i] + + for key in ['name', 'id']: + self.assertEqual(flavor_elem.get(key), str(flavor_dict[key])) + + link_nodes = flavor_elem.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(flavor_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + def test_index_empty(self): + serializer = flavors.MinimalFlavorsTemplate() + + fixture = { + "flavors": [], + } + + output = serializer.serialize(fixture) + root = etree.XML(output) + xmlutil.validate_schema(root, 'flavors_index') + flavor_elems = root.findall('{0}flavor'.format(NS)) + self.assertEqual(len(flavor_elems), 0) + + +class DisabledFlavorsWithRealDBTest(test.TestCase): + """ + Tests that disabled flavors should not be shown nor listed. + """ + def setUp(self): + super(DisabledFlavorsWithRealDBTest, self).setUp() + self.controller = flavors.FlavorsController() + + # Add a new disabled type to the list of flavors + self.req = fakes.HTTPRequestV3.blank('/flavors') + self.context = self.req.environ['nova.context'] + self.admin_context = context.get_admin_context() + + self.disabled_type = self._create_disabled_instance_type() + self.inst_types = db.api.instance_type_get_all(self.admin_context) + + def tearDown(self): + db.api.instance_type_destroy(self.admin_context, + self.disabled_type['name']) + + super(DisabledFlavorsWithRealDBTest, self).tearDown() + + def _create_disabled_instance_type(self): + inst_types = db.api.instance_type_get_all(self.admin_context) + + inst_type = inst_types[0] + + del inst_type['id'] + inst_type['name'] += '.disabled' + inst_type['flavorid'] = unicode(max( + [int(flavor['flavorid']) for flavor in inst_types]) + 1) + inst_type['disabled'] = True + + disabled_type = db.api.instance_type_create(self.admin_context, + inst_type) + + return disabled_type + + def test_index_should_not_list_disabled_flavors_to_user(self): + self.context.is_admin = False + + flavor_list = self.controller.index(self.req)['flavors'] + api_flavorids = set(f['id'] for f in flavor_list) + + db_flavorids = set(i['flavorid'] for i in self.inst_types) + disabled_flavorid = str(self.disabled_type['flavorid']) + + self.assert_(disabled_flavorid in db_flavorids) + self.assertEqual(db_flavorids - set([disabled_flavorid]), + api_flavorids) + + def test_index_should_list_disabled_flavors_to_admin(self): + self.context.is_admin = True + + flavor_list = self.controller.index(self.req)['flavors'] + api_flavorids = set(f['id'] for f in flavor_list) + + db_flavorids = set(i['flavorid'] for i in self.inst_types) + disabled_flavorid = str(self.disabled_type['flavorid']) + + self.assert_(disabled_flavorid in db_flavorids) + self.assertEqual(db_flavorids, api_flavorids) + + def test_show_should_include_disabled_flavor_for_user(self): + """ + Counterintuitively we should show disabled flavors to all users and not + just admins. The reason is that, when a user performs a server-show + request, we want to be able to display the pretty flavor name ('512 MB + Instance') and not just the flavor-id even if the flavor id has been + marked disabled. + """ + self.context.is_admin = False + + flavor = self.controller.show( + self.req, self.disabled_type['flavorid'])['flavor'] + + self.assertEqual(flavor['name'], self.disabled_type['name']) + + def test_show_should_include_disabled_flavor_for_admin(self): + self.context.is_admin = True + + flavor = self.controller.show( + self.req, self.disabled_type['flavorid'])['flavor'] + + self.assertEqual(flavor['name'], self.disabled_type['name']) + + +class ParseIsPublicTest(test.TestCase): + def setUp(self): + super(ParseIsPublicTest, self).setUp() + self.controller = flavors.FlavorsController() + + def assertPublic(self, expected, is_public): + self.assertIs(expected, self.controller._parse_is_public(is_public), + '%s did not return %s' % (is_public, expected)) + + def test_None(self): + self.assertPublic(True, None) + + def test_truthy(self): + self.assertPublic(True, True) + self.assertPublic(True, 't') + self.assertPublic(True, 'true') + self.assertPublic(True, 'yes') + self.assertPublic(True, '1') + + def test_falsey(self): + self.assertPublic(False, False) + self.assertPublic(False, 'f') + self.assertPublic(False, 'false') + self.assertPublic(False, 'no') + self.assertPublic(False, '0') + + def test_string_none(self): + self.assertPublic(None, 'none') + + def test_other(self): + self.assertRaises( + webob.exc.HTTPBadRequest, self.assertPublic, None, 'other') @@ -57,6 +57,7 @@ nova.api.v3.extensions = consoles = nova.api.openstack.compute.plugins.v3.consoles:Consoles extension_info = nova.api.openstack.compute.plugins.v3.extension_info:ExtensionInfo fixed_ips = nova.api.openstack.compute.plugins.v3.fixed_ips:FixedIPs + flavors = nova.api.openstack.compute.plugins.v3.flavors:Flavors ips = nova.api.openstack.compute.plugins.v3.ips:IPs keypairs = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs servers = nova.api.openstack.compute.plugins.v3.servers:Servers |