From 3e44d75660bcf018d60e2cc7b798e76efbc04905 Mon Sep 17 00:00:00 2001 From: Brandon Logan Date: Fri, 21 Oct 2016 13:15:31 -0500 Subject: [PATCH] Pecan: Find subresource controllers by parent Fix for finding subresource controller by parent and added functional tests Change-Id: I34bb1968fdc3daa75f8a60f5ad9aa68cc3414ad5 closes-Bug: #1633665 --- neutron/pecan_wsgi/controllers/resource.py | 12 +-- neutron/pecan_wsgi/controllers/utils.py | 4 +- neutron/pecan_wsgi/hooks/body_validation.py | 5 +- neutron/pecan_wsgi/startup.py | 23 +++--- .../functional/pecan_wsgi/test_controllers.py | 42 +++++++++++ neutron/tests/functional/pecan_wsgi/utils.py | 74 +++++++++++++------ 6 files changed, 116 insertions(+), 44 deletions(-) diff --git a/neutron/pecan_wsgi/controllers/resource.py b/neutron/pecan_wsgi/controllers/resource.py index c3e3bb18eb3..da06f34fa09 100644 --- a/neutron/pecan_wsgi/controllers/resource.py +++ b/neutron/pecan_wsgi/controllers/resource.py @@ -82,8 +82,9 @@ class ItemController(utils.NeutronPecanController): @utils.expose() def _lookup(self, collection, *remainder): request.context['collection'] = collection + collection_path = '/'.join([self.resource, collection]) controller = manager.NeutronManager.get_controller_for_resource( - collection) + collection_path) if not controller: if collection not in self._member_actions: LOG.warning(_LW("No controller found for: %s - returning" @@ -157,16 +158,17 @@ class CollectionsController(utils.NeutronPecanController): creator = self.plugin_bulk_creator key = self.collection data = {key: [{self.resource: res} for res in resources]} + creator_kwargs = {self.collection: data} else: creator = self.plugin_creator key = self.resource data = {key: resources[0]} + creator_kwargs = {self.resource: data} neutron_context = request.context['neutron_context'] creator_args = [neutron_context] - if 'parent_id' in request.context: - creator_args.append(request.context['parent_id']) - creator_args.append(data) - return {key: creator(*creator_args)} + if 'parent_id' in request.context and self._parent_id_name: + creator_kwargs[self._parent_id_name] = request.context['parent_id'] + return {key: creator(*creator_args, **creator_kwargs)} class MemberActionController(ItemController): diff --git a/neutron/pecan_wsgi/controllers/utils.py b/neutron/pecan_wsgi/controllers/utils.py index de8ced7eb71..64e9f2650a0 100644 --- a/neutron/pecan_wsgi/controllers/utils.py +++ b/neutron/pecan_wsgi/controllers/utils.py @@ -127,8 +127,8 @@ class NeutronPecanController(object): self.parent = parent_resource parent_resource = '_%s' % parent_resource if parent_resource else '' - self._parent_id_name = ('%s_id' % parent_resource - if parent_resource else None) + self._parent_id_name = ('%s_id' % self.parent + if self.parent else None) self._plugin_handlers = { self.LIST: 'get%s_%s' % (parent_resource, self.collection), self.SHOW: 'get%s_%s' % (parent_resource, self.resource) diff --git a/neutron/pecan_wsgi/hooks/body_validation.py b/neutron/pecan_wsgi/hooks/body_validation.py index da156e74809..621d485a4a1 100644 --- a/neutron/pecan_wsgi/hooks/body_validation.py +++ b/neutron/pecan_wsgi/hooks/body_validation.py @@ -18,7 +18,7 @@ from oslo_serialization import jsonutils from pecan import hooks from neutron.api.v2 import base as v2_base -from neutron import manager +from neutron.pecan_wsgi.hooks import utils LOG = log.getLogger(__name__) @@ -51,8 +51,7 @@ class BodyValidationHook(hooks.PecanHook): # member action is being processed or on agent scheduler operations return # Prepare data to be passed to the plugin from request body - controller = manager.NeutronManager.get_controller_for_resource( - collection) + controller = utils.get_controller(state) data = v2_base.Controller.prepare_request_body( neutron_context, json_data, diff --git a/neutron/pecan_wsgi/startup.py b/neutron/pecan_wsgi/startup.py index 34287419f4f..343ec2c5304 100644 --- a/neutron/pecan_wsgi/startup.py +++ b/neutron/pecan_wsgi/startup.py @@ -59,7 +59,18 @@ def initialize_all(): for ext_res in resources: path_prefix = ext_res.path_prefix.strip('/') collection = ext_res.collection - if manager.NeutronManager.get_controller_for_resource(collection): + # Retrieving the parent resource. It is expected the format of + # the parent resource to be: + # {'collection_name': 'name-of-collection', + # 'member_name': 'name-of-resource'} + # collection_name does not appear to be used in the legacy code + # inside the controller logic, so we can assume we do not need it. + parent = ext_res.parent or {} + parent_resource = parent.get('member_name') + collection_key = collection + if parent_resource: + collection_key = '/'.join([parent_resource, collection]) + if manager.NeutronManager.get_controller_for_resource(collection_key): # This is a collection that already has a pecan controller, we # do not need to do anything else continue @@ -71,14 +82,6 @@ def initialize_all(): plugin = legacy_controller.plugin attr_info = legacy_controller.attr_info member_actions = legacy_controller.member_actions - # Retrieving the parent resource. It is expected the format of - # the parent resource to be: - # {'collection_name': 'name-of-collection', - # 'member_name': 'name-of-resource'} - # collection_name does not appear to be used in the legacy code - # inside the controller logic, so we can assume we do not need it. - parent = legacy_controller.parent or {} - parent_resource = parent.get('member_name') new_controller = res_ctrl.CollectionsController( collection, resource, resource_info=attr_info, parent_resource=parent_resource, member_actions=member_actions) @@ -93,7 +96,7 @@ def initialize_all(): LOG.warning(_LW("Unknown controller type encountered %s. It will" "be ignored."), legacy_controller) manager.NeutronManager.set_controller_for_resource( - collection, new_controller) + collection_key, new_controller) # Certain policy checks require that the extensions are loaded # and the RESOURCE_ATTRIBUTE_MAP populated before they can be diff --git a/neutron/tests/functional/pecan_wsgi/test_controllers.py b/neutron/tests/functional/pecan_wsgi/test_controllers.py index f10a2826355..f24bcdc730d 100644 --- a/neutron/tests/functional/pecan_wsgi/test_controllers.py +++ b/neutron/tests/functional/pecan_wsgi/test_controllers.py @@ -894,3 +894,45 @@ class TestMemberActionController(test_functional.PecanFunctionalTest): url = '/v2.0/{}/something/put_meh.json'.format(self.collection) resp = self.app.get(url, expect_errors=True) self.assertEqual(405, resp.status_int) + + +class TestParentSubresourceController(test_functional.PecanFunctionalTest): + def setUp(self): + fake_ext = pecan_utils.FakeExtension() + fake_plugin = pecan_utils.FakePlugin() + plugins = {pecan_utils.FakePlugin.PLUGIN_TYPE: fake_plugin} + new_extensions = {fake_ext.get_alias(): fake_ext} + super(TestParentSubresourceController, self).setUp( + service_plugins=plugins, extensions=new_extensions) + policy.init() + policy._ENFORCER.set_rules( + oslo_policy.Rules.from_dict( + {'get_fake_duplicate': '', + 'get_meh_meh_fake_duplicates': ''}), + overwrite=False) + self.addCleanup(policy.reset) + hyphen_collection = pecan_utils.FakeExtension.HYPHENATED_COLLECTION + self.collection = hyphen_collection.replace('_', '-') + self.fake_collection = (pecan_utils.FakeExtension. + FAKE_PARENT_SUBRESOURCE_COLLECTION) + + def test_get_duplicate_parent_resource(self): + url = '/v2.0/{}'.format(self.fake_collection) + resp = self.app.get(url) + self.assertEqual(200, resp.status_int) + self.assertEqual({'fake_duplicates': [{'fake': 'fakeduplicates'}]}, + resp.json) + + def test_get_duplicate_parent_resource_item(self): + url = '/v2.0/{}/something'.format(self.fake_collection) + resp = self.app.get(url) + self.assertEqual(200, resp.status_int) + self.assertEqual({'fake_duplicate': {'fake': 'something'}}, resp.json) + + def test_get_parent_resource_and_duplicate_subresources(self): + url = '/v2.0/{0}/something/{1}'.format(self.collection, + self.fake_collection) + resp = self.app.get(url) + self.assertEqual(200, resp.status_int) + self.assertEqual({'fake_duplicates': [{'fake': 'something'}]}, + resp.json) diff --git a/neutron/tests/functional/pecan_wsgi/utils.py b/neutron/tests/functional/pecan_wsgi/utils.py index 8bd0c240712..3794e1d6f34 100644 --- a/neutron/tests/functional/pecan_wsgi/utils.py +++ b/neutron/tests/functional/pecan_wsgi/utils.py @@ -109,23 +109,35 @@ class FakeExtension(extensions.ExtensionDescriptor): HYPHENATED_RESOURCE = 'meh_meh' HYPHENATED_COLLECTION = HYPHENATED_RESOURCE + 's' + FAKE_PARENT_SUBRESOURCE_COLLECTION = 'fake_duplicates' + FAKE_SUB_RESOURCE_COLLECTION = 'fake_subresources' + + RESOURCE_ATTRIBUTE_MAP = { + 'meh_mehs': { + 'fake': {'is_visible': True} + }, + 'fake_duplicates': { + 'fake': {'is_visible': True} + } + } SUB_RESOURCE_ATTRIBUTE_MAP = { 'fake_subresources': { 'parent': { 'collection_name': ( - HYPHENATED_COLLECTION), - 'member_name': HYPHENATED_RESOURCE}, + 'meh_mehs'), + 'member_name': 'meh_meh'}, 'parameters': {'foo': {'is_visible': True}, 'bar': {'is_visible': True} } - } - } - FAKE_SUB_RESOURCE_COLLECTION = 'fake_subresources' - - RAM = { - HYPHENATED_COLLECTION: { - 'fake': {'is_visible': True} + }, + 'fake_duplicates': { + 'parent': { + 'collection_name': ( + 'meh_mehs'), + 'member_name': 'meh_meh'}, + 'parameters': {'fake': {'is_visible': True} + } } } @@ -146,21 +158,26 @@ class FakeExtension(extensions.ExtensionDescriptor): return "meh" def get_resources(self): - collection = self.HYPHENATED_COLLECTION.replace('_', '-') - params = self.RAM.get(self.HYPHENATED_COLLECTION, {}) - attributes.PLURALS.update({self.HYPHENATED_COLLECTION: - self.HYPHENATED_RESOURCE}) - member_actions = {'put_meh': 'PUT', 'boo_meh': 'GET'} + """Returns Ext Resources.""" + resources = [] fake_plugin = FakePlugin() - controller = base.create_resource( - collection, self.HYPHENATED_RESOURCE, FakePlugin(), - params, allow_bulk=True, allow_pagination=True, - allow_sorting=True, member_actions=member_actions) - resources = [extensions.ResourceExtension( - collection, controller, attr_map=params, - member_actions=member_actions)] + for collection_name in self.RESOURCE_ATTRIBUTE_MAP: + resource_name = collection_name[:-1] + params = self.RESOURCE_ATTRIBUTE_MAP.get(collection_name, {}) + attributes.PLURALS.update({collection_name: resource_name}) + member_actions = {'put_meh': 'PUT', 'boo_meh': 'GET'} + if collection_name == self.HYPHENATED_COLLECTION: + collection_name = collection_name.replace('_', '-') + controller = base.create_resource( + collection_name, resource_name, fake_plugin, + params, allow_bulk=True, allow_pagination=True, + allow_sorting=True, member_actions=member_actions) + resource = extensions.ResourceExtension( + collection_name, controller, attr_map=params) + resources.append(resource) + for collection_name in self.SUB_RESOURCE_ATTRIBUTE_MAP: - resource_name = collection_name + resource_name = collection_name[:-1] parent = self.SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get( 'parent') params = self.SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get( @@ -170,7 +187,6 @@ class FakeExtension(extensions.ExtensionDescriptor): fake_plugin, params, allow_bulk=True, parent=parent) - resource = extensions.ResourceExtension( collection_name, controller, parent, @@ -182,7 +198,7 @@ class FakeExtension(extensions.ExtensionDescriptor): def get_extended_resources(self, version): if version == "2.0": - return self.RAM + return self.RESOURCE_ATTRIBUTE_MAP else: return {} @@ -202,6 +218,16 @@ class FakePlugin(object): def get_meh_mehs(self, context, filters=None, fields=None): return [{'fake': 'fake'}] + def get_fake_duplicate(self, context, id_, fields=None): + return {'fake': id_} + + def get_fake_duplicates(self, context, filters=None, fields=None): + return [{'fake': 'fakeduplicates'}] + + def get_meh_meh_fake_duplicates(self, context, id_, fields=None, + filters=None): + return [{'fake': id_}] + def get_meh_meh_fake_subresources(self, context, id_, fields=None, filters=None): return {'foo': id_}