diff options
| author | unicell <unicell@gmail.com> | 2012-08-13 20:19:54 +0800 |
|---|---|---|
| committer | unicell <unicell@gmail.com> | 2012-08-27 23:45:05 +0800 |
| commit | 34c012c709cc5ae577330c7d67ba060293158210 (patch) | |
| tree | e04765cbb3e09e9047c51aa9e03d4b1db15ac80c /nova/tests | |
| parent | 5e012d8d45935b68a5ce5d50ed043d4bb8066cf8 (diff) | |
Implement project specific flavors API
blueprint project-specific-flavors
This change implements API extension to manage project specific flavor
types, so that non-public flavor type can only see by projects with
access rights.
Change-Id: Ie2d2c605065b0c76897f843a4548a0c984a05f1a
Diffstat (limited to 'nova/tests')
| -rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_flavor_access.py | 299 | ||||
| -rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_flavor_manage.py | 10 | ||||
| -rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_flavorextradata.py | 6 | ||||
| -rw-r--r-- | nova/tests/api/openstack/compute/test_extensions.py | 1 | ||||
| -rw-r--r-- | nova/tests/policy.json | 1 | ||||
| -rw-r--r-- | nova/tests/test_utils.py | 9 |
6 files changed, 322 insertions, 4 deletions
diff --git a/nova/tests/api/openstack/compute/contrib/test_flavor_access.py b/nova/tests/api/openstack/compute/contrib/test_flavor_access.py new file mode 100644 index 000000000..0bf1f1b66 --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_flavor_access.py @@ -0,0 +1,299 @@ +# Copyright 2012 OpenStack LLC. +# 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 datetime + +from lxml import etree +from webob import exc + +from nova.api.openstack.compute.contrib import flavor_access +from nova.api.openstack.compute import flavors +from nova.compute import instance_types +from nova import context +from nova import exception +from nova import test +from nova.tests.api.openstack import fakes + + +def generate_instance_type(flavorid, ispublic): + return { + 'id': flavorid, + 'flavorid': str(flavorid), + 'root_gb': 1, + 'ephemeral_gb': 1, + 'name': u'test', + 'deleted': False, + 'created_at': datetime.datetime(2012, 1, 1, 1, 1, 1, 1), + 'updated_at': None, + 'memory_mb': 512, + 'vcpus': 1, + 'swap': 512, + 'rxtx_factor': 1.0, + 'extra_specs': {}, + 'deleted_at': None, + 'vcpu_weight': None, + 'is_public': bool(ispublic) + } + + +INSTANCE_TYPES = { + '0': generate_instance_type(0, True), + '1': generate_instance_type(1, True), + '2': generate_instance_type(2, False), + '3': generate_instance_type(3, False)} + + +ACCESS_LIST = [{'flavor_id': '2', 'project_id': 'proj2'}, + {'flavor_id': '2', 'project_id': 'proj3'}, + {'flavor_id': '3', 'project_id': 'proj3'}] + + +def fake_get_instance_type_access_by_flavor_id(flavorid): + res = [] + for access in ACCESS_LIST: + if access['flavor_id'] == flavorid: + res.append(access) + return res + + +def fake_get_instance_type_by_flavor_id(flavorid): + return INSTANCE_TYPES[flavorid] + + +def _has_flavor_access(flavorid, projectid): + for access in ACCESS_LIST: + if access['flavor_id'] == flavorid and \ + access['project_id'] == projectid: + return True + return False + + +def fake_get_all_types(context, inactive=0, filters=None): + if filters == None or filters['is_public'] == None: + return INSTANCE_TYPES + + res = {} + for k, v in INSTANCE_TYPES.iteritems(): + if filters['is_public'] and _has_flavor_access(k, context.project_id): + res.update({k: v}) + continue + if v['is_public'] == filters['is_public']: + res.update({k: v}) + + return res + + +class FakeRequest(object): + environ = {"nova.context": context.get_admin_context()} + + +class FlavorAccessTest(test.TestCase): + def setUp(self): + super(FlavorAccessTest, self).setUp() + self.flavor_controller = flavors.Controller() + self.flavor_access_controller = flavor_access.FlavorAccessController() + self.flavor_action_controller = flavor_access.FlavorActionController() + self.req = FakeRequest() + self.context = self.req.environ['nova.context'] + self.stubs.Set(instance_types, 'get_instance_type_by_flavor_id', + fake_get_instance_type_by_flavor_id) + self.stubs.Set(instance_types, 'get_all_types', fake_get_all_types) + self.stubs.Set(instance_types, 'get_instance_type_access_by_flavor_id', + fake_get_instance_type_access_by_flavor_id) + + def _verify_flavor_list(self, result, expected): + # result already sorted by flavor_id + self.assertEqual(len(result), len(expected)) + + for d1, d2 in zip(result, expected): + self.assertEqual(d1['id'], d2['id']) + + def test_list_flavor_access_public(self): + # query os-flavor-access on public flavor should return 404 + req = fakes.HTTPRequest.blank('/v2/fake/flavors/os-flavor-access', + use_admin_context=True) + self.assertRaises(exc.HTTPNotFound, + self.flavor_access_controller.index, + self.req, '1') + + def test_list_flavor_access_private(self): + expected = {'flavor_access': [ + {'flavor_id': '2', 'tenant_id': 'proj2'}, + {'flavor_id': '2', 'tenant_id': 'proj3'}]} + result = self.flavor_access_controller.index(self.req, '2') + self.assertEqual(result, expected) + + def test_list_flavor_with_admin_default_proj1(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors', + use_admin_context=True) + req.environ['nova.context'].project_id = 'proj1' + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_admin_default_proj2(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}, {'id': '2'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors', + use_admin_context=True) + req.environ['nova.context'].project_id = 'proj2' + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_admin_ispublic_true(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=true', + use_admin_context=True) + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_admin_ispublic_false(self): + expected = {'flavors': [{'id': '2'}, {'id': '3'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=false', + use_admin_context=True) + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_admin_ispublic_false_proj2(self): + expected = {'flavors': [{'id': '2'}, {'id': '3'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=false', + use_admin_context=True) + req.environ['nova.context'].project_id = 'proj2' + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_admin_ispublic_none(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}, {'id': '2'}, + {'id': '3'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=none', + use_admin_context=True) + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_no_admin_default(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors', + use_admin_context=False) + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_no_admin_ispublic_true(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=true', + use_admin_context=False) + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_no_admin_ispublic_false(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=false', + use_admin_context=False) + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_list_flavor_with_no_admin_ispublic_none(self): + expected = {'flavors': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/flavors?is_public=none', + use_admin_context=False) + result = self.flavor_controller.index(req) + self._verify_flavor_list(result['flavors'], expected['flavors']) + + def test_add_tenant_access(self): + def stub_add_instance_type_access(flavorid, projectid, ctxt=None): + self.assertEqual('3', flavorid, "flavorid") + self.assertEqual("proj2", projectid, "projectid") + self.stubs.Set(instance_types, 'add_instance_type_access', + stub_add_instance_type_access) + expected = {'flavor_access': + [{'flavor_id': '3', 'tenant_id': 'proj3'}]} + body = {'addTenantAccess': {'tenant': 'proj2'}} + req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action', + use_admin_context=True) + result = self.flavor_action_controller.\ + _addTenantAccess(req, '3', body) + self.assertEqual(result, expected) + + def test_add_tenant_access_with_already_added_access(self): + def stub_add_instance_type_access(flavorid, projectid, ctxt=None): + raise exception.FlavorAccessExists() + self.stubs.Set(instance_types, 'add_instance_type_access', + stub_add_instance_type_access) + body = {'addTenantAccess': {'tenant': 'proj2'}} + req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action', + use_admin_context=True) + self.assertRaises(exc.HTTPConflict, + self.flavor_action_controller._addTenantAccess, + self.req, '3', body) + + def test_remove_tenant_access_with_bad_access(self): + def stub_remove_instance_type_access(flavorid, projectid, ctxt=None): + self.assertEqual('3', flavorid, "flavorid") + self.assertEqual("proj2", projectid, "projectid") + expected = {'flavor_access': [ + {'flavor_id': '3', 'tenant_id': 'proj3'}]} + self.stubs.Set(instance_types, 'remove_instance_type_access', + stub_remove_instance_type_access) + body = {'removeTenantAccess': {'tenant': 'proj2'}} + req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action', + use_admin_context=True) + result = self.flavor_action_controller.\ + _addTenantAccess(req, '3', body) + self.assertEqual(result, expected) + + def test_remove_tenant_access_with_bad_access(self): + def stub_remove_instance_type_access(flavorid, projectid, ctxt=None): + raise exception.FlavorAccessNotFound() + self.stubs.Set(instance_types, 'remove_instance_type_access', + stub_remove_instance_type_access) + body = {'removeTenantAccess': {'tenant': 'proj2'}} + req = fakes.HTTPRequest.blank('/v2/fake/flavors/2/action', + use_admin_context=True) + self.assertRaises(exc.HTTPNotFound, + self.flavor_action_controller._removeTenantAccess, + self.req, '3', body) + + +class FlavorAccessSerializerTest(test.TestCase): + def test_xml_declaration(self): + access_list = [{'flavor_id': '2', 'tenant_id': 'proj2'}] + serializer = flavor_access.FlavorAccessTemplate() + output = serializer.serialize(access_list) + has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>") + self.assertTrue(has_dec) + + def test_serializer_empty(self): + access_list = [] + + serializer = flavor_access.FlavorAccessTemplate() + text = serializer.serialize(access_list) + tree = etree.fromstring(text) + self.assertEqual(len(tree), 0) + + def test_serializer(self): + access_list = [{'flavor_id': '2', 'tenant_id': 'proj2'}, + {'flavor_id': '2', 'tenant_id': 'proj3'}] + + serializer = flavor_access.FlavorAccessTemplate() + text = serializer.serialize(access_list) + tree = etree.fromstring(text) + + self.assertEqual('flavor_access', tree.tag) + self.assertEqual(len(access_list), len(tree)) + + for i in range(len(access_list)): + self.assertEqual('access', tree[i].tag) + self.assertEqual(access_list[i]['flavor_id'], + tree[i].get('flavor_id')) + self.assertEqual(access_list[i]['tenant_id'], + tree[i].get('tenant_id')) diff --git a/nova/tests/api/openstack/compute/contrib/test_flavor_manage.py b/nova/tests/api/openstack/compute/contrib/test_flavor_manage.py index 140efc325..84ff8a1ea 100644 --- a/nova/tests/api/openstack/compute/contrib/test_flavor_manage.py +++ b/nova/tests/api/openstack/compute/contrib/test_flavor_manage.py @@ -46,7 +46,8 @@ def fake_get_instance_type_by_flavor_id(flavorid): 'extra_specs': {}, 'deleted_at': None, 'vcpu_weight': None, - 'id': 7 + 'id': 7, + 'is_public': True } @@ -55,7 +56,7 @@ def fake_destroy(flavorname): def fake_create(name, memory_mb, vcpus, root_gb, ephemeral_gb, - flavorid, swap, rxtx_factor): + flavorid, swap, rxtx_factor, is_public): newflavor = fake_get_instance_type_by_flavor_id(flavorid) newflavor["name"] = name @@ -65,6 +66,7 @@ def fake_create(name, memory_mb, vcpus, root_gb, ephemeral_gb, newflavor["ephemeral_gb"] = int(ephemeral_gb) newflavor["swap"] = swap newflavor["rxtx_factor"] = float(rxtx_factor) + newflavor["is_public"] = bool(is_public) return newflavor @@ -100,6 +102,7 @@ class FlavorManageTest(test.TestCase): "id": 1234, "swap": 512, "rxtx_factor": 1, + "os-flavor-access:is_public": True, } } @@ -124,11 +127,12 @@ class FlavorManageTest(test.TestCase): "id": 1235, "swap": 512, "rxtx_factor": 1, + "os-flavor-access:is_public": True, } } def fake_create(name, memory_mb, vcpus, root_gb, ephemeral_gb, - flavorid, swap, rxtx_factor): + flavorid, swap, rxtx_factor, is_public): raise exception.InstanceTypeExists() self.stubs.Set(instance_types, "create", fake_create) diff --git a/nova/tests/api/openstack/compute/contrib/test_flavorextradata.py b/nova/tests/api/openstack/compute/contrib/test_flavorextradata.py index 4f24b08ad..3c4a84e2a 100644 --- a/nova/tests/api/openstack/compute/contrib/test_flavorextradata.py +++ b/nova/tests/api/openstack/compute/contrib/test_flavorextradata.py @@ -39,7 +39,8 @@ def fake_get_instance_type_by_flavor_id(flavorid): 'rxtx_factor': 1.0, 'extra_specs': {}, 'deleted_at': None, - 'vcpu_weight': None + 'vcpu_weight': None, + 'is_public': True } @@ -72,6 +73,7 @@ class FlavorextradataTest(test.TestCase): 'OS-FLV-EXT-DATA:ephemeral': 1, 'swap': 512, 'rxtx_factor': 1, + 'os-flavor-access:is_public': True, } } @@ -93,6 +95,7 @@ class FlavorextradataTest(test.TestCase): 'OS-FLV-EXT-DATA:ephemeral': 1, 'swap': 512, 'rxtx_factor': 1, + 'os-flavor-access:is_public': True, }, { 'id': '2', @@ -103,6 +106,7 @@ class FlavorextradataTest(test.TestCase): 'OS-FLV-EXT-DATA:ephemeral': 1, 'swap': 512, 'rxtx_factor': 1, + 'os-flavor-access:is_public': True, }, ] diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index 4110d3ea3..200f56a56 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -166,6 +166,7 @@ class ExtensionControllerTest(ExtensionTestCase): "DiskConfig", "ExtendedStatus", "ExtendedServerAttributes", + "FlavorAccess", "FlavorExtraSpecs", "FlavorExtraData", "FlavorManage", diff --git a/nova/tests/policy.json b/nova/tests/policy.json index 61ec0152a..08577ed04 100644 --- a/nova/tests/policy.json +++ b/nova/tests/policy.json @@ -92,6 +92,7 @@ "compute_extension:disk_config": [], "compute_extension:extended_server_attributes": [], "compute_extension:extended_status": [], + "compute_extension:flavor_access": [], "compute_extension:flavorextradata": [], "compute_extension:flavorextraspecs": [], "compute_extension:flavormanage": [], diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index b4e1ebb3d..c7a323875 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -329,8 +329,17 @@ class GenericUtilsTestCase(test.TestCase): self.assertTrue(utils.bool_from_str('true')) self.assertTrue(utils.bool_from_str('True')) self.assertTrue(utils.bool_from_str('tRuE')) + self.assertTrue(utils.bool_from_str('yes')) + self.assertTrue(utils.bool_from_str('Yes')) + self.assertTrue(utils.bool_from_str('YeS')) + self.assertTrue(utils.bool_from_str('y')) + self.assertTrue(utils.bool_from_str('Y')) self.assertFalse(utils.bool_from_str('False')) self.assertFalse(utils.bool_from_str('false')) + self.assertFalse(utils.bool_from_str('no')) + self.assertFalse(utils.bool_from_str('No')) + self.assertFalse(utils.bool_from_str('n')) + self.assertFalse(utils.bool_from_str('N')) self.assertFalse(utils.bool_from_str('0')) self.assertFalse(utils.bool_from_str(None)) self.assertFalse(utils.bool_from_str('junk')) |
