From 6e908dd417d3a1f330cb4c041547bc1bd2c13c1f Mon Sep 17 00:00:00 2001 From: Brandon Logan Date: Wed, 20 Jul 2016 01:46:49 -0500 Subject: [PATCH] Pecan: Handle member actions The legacy pecan uri routing allowed the definition of member actions. These are API resources that hang off a particular resource_id. An example of this is: PUT /routers/{router_id}/add_router_interface The legacy seemed to do only PUTs and GETs so that has been implemented in Pecan. Other methods can easily be added if needed. Also, with the generic handling of this came the decision to remove the specific pecan routers controller because it's only function was to facilitate its member actions to work. It is no longer needed. Change-Id: If776476545edca0c4c43ce3969bb5d1af79f4382 --- neutron/api/v2/base.py | 4 + neutron/extensions/l3.py | 15 --- neutron/pecan_wsgi/controllers/__init__.py | 2 - neutron/pecan_wsgi/controllers/resource.py | 92 +++++++++++++-- neutron/pecan_wsgi/controllers/router.py | 111 ------------------ neutron/pecan_wsgi/controllers/utils.py | 3 +- neutron/pecan_wsgi/hooks/notifier.py | 5 + .../pecan_wsgi/hooks/policy_enforcement.py | 33 +++--- neutron/pecan_wsgi/hooks/query_parameters.py | 4 +- neutron/pecan_wsgi/hooks/utils.py | 30 +++++ neutron/pecan_wsgi/startup.py | 3 +- .../functional/pecan_wsgi/test_controllers.py | 48 ++++++++ .../tests/functional/pecan_wsgi/test_hooks.py | 6 +- neutron/tests/functional/pecan_wsgi/utils.py | 15 ++- 14 files changed, 206 insertions(+), 165 deletions(-) delete mode 100644 neutron/pecan_wsgi/controllers/router.py create mode 100644 neutron/pecan_wsgi/hooks/utils.py diff --git a/neutron/api/v2/base.py b/neutron/api/v2/base.py index d84d237e121..3b1ea7b1664 100644 --- a/neutron/api/v2/base.py +++ b/neutron/api/v2/base.py @@ -71,6 +71,10 @@ class Controller(object): def attr_info(self): return self._attr_info + @property + def member_actions(self): + return self._member_actions + def __init__(self, plugin, collection, resource, attr_info, allow_bulk=False, member_actions=None, parent=None, allow_pagination=False, allow_sorting=False): diff --git a/neutron/extensions/l3.py b/neutron/extensions/l3.py index 50d8aefa6b3..a8d5029bed4 100644 --- a/neutron/extensions/l3.py +++ b/neutron/extensions/l3.py @@ -24,9 +24,6 @@ from neutron.api import extensions from neutron.api.v2 import attributes as attr from neutron.api.v2 import resource_helper from neutron.conf import quota -from neutron import manager -from neutron.pecan_wsgi import controllers -from neutron.pecan_wsgi.controllers import utils as pecan_utils from neutron.plugins.common import constants @@ -201,18 +198,6 @@ class L3(extensions.ExtensionDescriptor): super(L3, self).update_attributes_map( attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) - @classmethod - def get_pecan_resources(cls): - plugin = manager.NeutronManager.get_service_plugins()[ - constants.L3_ROUTER_NAT] - router_controller = controllers.RoutersController() - fip_controller = controllers.CollectionsController( - FLOATINGIPS, FLOATINGIP) - return [pecan_utils.PecanResourceExtension( - ROUTERS, router_controller, plugin), - pecan_utils.PecanResourceExtension( - FLOATINGIPS, fip_controller, plugin)] - def get_extended_resources(self, version): if version == "2.0": return RESOURCE_ATTRIBUTE_MAP diff --git a/neutron/pecan_wsgi/controllers/__init__.py b/neutron/pecan_wsgi/controllers/__init__.py index 1f840d77223..f8ea6dd7223 100644 --- a/neutron/pecan_wsgi/controllers/__init__.py +++ b/neutron/pecan_wsgi/controllers/__init__.py @@ -12,9 +12,7 @@ from neutron.pecan_wsgi.controllers import quota from neutron.pecan_wsgi.controllers import resource -from neutron.pecan_wsgi.controllers import router CollectionsController = resource.CollectionsController QuotasController = quota.QuotasController -RoutersController = router.RoutersController diff --git a/neutron/pecan_wsgi/controllers/resource.py b/neutron/pecan_wsgi/controllers/resource.py index dd48333236f..6909fb65950 100644 --- a/neutron/pecan_wsgi/controllers/resource.py +++ b/neutron/pecan_wsgi/controllers/resource.py @@ -27,10 +27,11 @@ LOG = logging.getLogger(__name__) class ItemController(utils.NeutronPecanController): def __init__(self, resource, item, plugin=None, resource_info=None, - parent_resource=None): + parent_resource=None, member_actions=None): super(ItemController, self).__init__(None, resource, plugin=plugin, resource_info=resource_info, - parent_resource=parent_resource) + parent_resource=parent_resource, + member_actions=member_actions) self.item = item @utils.expose(generic=True) @@ -84,11 +85,24 @@ class ItemController(utils.NeutronPecanController): controller = manager.NeutronManager.get_controller_for_resource( collection) if not controller: - LOG.warning(_LW("No controller found for: %s - returning response " - "code 404"), collection) - pecan.abort(404) + if collection not in self._member_actions: + LOG.warning(_LW("No controller found for: %s - returning" + "response code 404"), collection) + pecan.abort(404) + # collection is a member action, so we create a new controller + # for it. + method = self._member_actions[collection] + kwargs = {'plugin': self.plugin, + 'resource_info': self.resource_info} + if method == 'PUT': + kwargs['update_action'] = collection + elif method == 'GET': + kwargs['show_action'] = collection + controller = MemberActionController( + self.resource, self.item, self, **kwargs) + else: + request.context['parent_id'] = request.context['resource_id'] request.context['resource'] = controller.resource - request.context['parent_id'] = request.context['resource_id'] return controller, remainder @@ -104,11 +118,10 @@ class CollectionsController(utils.NeutronPecanController): request.context['uri_identifiers'][uri_identifier] = item return (self.item_controller_class( self.resource, item, resource_info=self.resource_info, - # NOTE(tonytan4ever): item needs to share the same - # parent as collection - parent_resource=self.parent - ), - remainder) + # NOTE(tonytan4ever): item needs to share the same + # parent as collection + parent_resource=self.parent, + member_actions=self._member_actions), remainder) @utils.expose(generic=True) def index(self, *args, **kwargs): @@ -154,3 +167,60 @@ class CollectionsController(utils.NeutronPecanController): creator_args.append(request.context['parent_id']) creator_args.append(data) return {key: creator(*creator_args)} + + +class MemberActionController(ItemController): + @property + def plugin_shower(self): + # NOTE(blogan): Do an explicit check for the _show_action because + # pecan will see the plugin_shower property as a possible custom route + # and try to evaluate it, which causes the code block to be executed. + # If _show_action is None, getattr throws an exception and fails a + # request. + if self._show_action: + return getattr(self.plugin, self._show_action) + + @property + def plugin_updater(self): + if self._update_action: + return getattr(self.plugin, self._update_action) + + def __init__(self, resource, item, parent_controller, plugin=None, + resource_info=None, show_action=None, update_action=None): + super(MemberActionController, self).__init__( + resource, item, plugin=plugin, resource_info=resource_info) + self._show_action = show_action + self._update_action = update_action + self.parent_controller = parent_controller + + @utils.expose(generic=True) + def index(self, *args, **kwargs): + if not self._show_action: + pecan.abort(405) + neutron_context = request.context['neutron_context'] + fields = request.context['query_params'].get('fields') + return self.plugin_shower(neutron_context, self.item, fields=fields) + + @utils.when(index, method='PUT') + def put(self, *args, **kwargs): + if not self._update_action: + LOG.debug("Action %(action)s is not defined on resource " + "%(resource)s", + {'action': self._update_action, + 'resource': self.resource}) + pecan.abort(405) + neutron_context = request.context['neutron_context'] + LOG.debug("Processing member action %(action)s for resource " + "%(resource)s identified by %(item)s", + {'action': self._update_action, + 'resource': self.resource, + 'item': self.item}) + return self.plugin_updater(neutron_context, self.item, + request.context['request_data']) + + @utils.when(index, method='HEAD') + @utils.when(index, method='POST') + @utils.when(index, method='PATCH') + @utils.when(index, method='DELETE') + def not_supported(self): + return super(MemberActionController, self).not_supported() diff --git a/neutron/pecan_wsgi/controllers/router.py b/neutron/pecan_wsgi/controllers/router.py deleted file mode 100644 index 68873de4987..00000000000 --- a/neutron/pecan_wsgi/controllers/router.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (c) 2015 Taturiello Consulting, Meh. -# 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 neutron._i18n import _LE -from oslo_log import log -import pecan -from pecan import request - -from neutron import manager -from neutron.pecan_wsgi.controllers import resource -from neutron.pecan_wsgi.controllers import utils - -LOG = log.getLogger(__name__) - - -class RouterController(resource.ItemController): - """Customize ResourceController for member actions""" - - ### Pecan generic controllers don't work very well with inheritance - - @utils.expose(generic=True) - def index(self, *args, **kwargs): - return super(RouterController, self).index(*args, **kwargs) - - @utils.when(index, method='HEAD') - @utils.when(index, method='POST') - @utils.when(index, method='PATCH') - def not_supported(self): - return super(RouterController, self).not_supported() - - @utils.when(index, method='PUT') - def put(self, *args, **kwargs): - return super(RouterController, self).put(*args, **kwargs) - - @utils.when(index, method='DELETE') - def delete(self): - return super(RouterController, self).delete() - - @utils.expose() - def _lookup(self, member_action, *remainder): - # This check is mainly for the l3-agents resource. If there isn't - # a controller for it then we'll just assume its a member action. - controller = manager.NeutronManager.get_controller_for_resource( - member_action) - if not controller: - controller = RouterMemberActionController( - self.resource, self.item, member_action) - return controller, remainder - - -class RoutersController(resource.CollectionsController): - - item_controller_class = RouterController - - def __init__(self): - super(RoutersController, self).__init__('routers', 'router') - - -class RouterMemberActionController(resource.ItemController): - - def __init__(self, resource, item, member_action): - super(RouterMemberActionController, self).__init__(resource, item) - self.member_action = member_action - - @utils.expose(generic=True) - def index(self, *args, **kwargs): - pecan.abort(405) - - @utils.when(index, method='HEAD') - @utils.when(index, method='POST') - @utils.when(index, method='PATCH') - def not_supported(self): - return super(RouterMemberActionController, self).not_supported() - - @utils.when(index, method='PUT') - def put(self, *args, **kwargs): - neutron_context = request.context['neutron_context'] - LOG.debug("Processing member action %(action)s for resource " - "%(resource)s identified by %(item)s", - {'action': self.member_action, - 'resource': self.resource, - 'item': self.item}) - # NOTE(salv-orlando): The following simply verify that the plugin - # has a method for a given action. It therefore enables plugins to - # implement actions which are not part of the API specification. - # Unfortunately the API extension descriptor does not do a good job - # of sanctioning which actions are available on a given resource. - # TODO(salv-orlando): prevent plugins from implementing actions - # which are not part of the Neutron API spec - try: - member_action_method = getattr(self.plugin, self.member_action) - return member_action_method(neutron_context, self.item, - request.context['request_data']) - except AttributeError: - LOG.error(_LE("Action %(action)s is not defined on resource " - "%(resource)s"), - {'action': self.member_action, - 'resource': self.resource}) - pecan.abort(404) diff --git a/neutron/pecan_wsgi/controllers/utils.py b/neutron/pecan_wsgi/controllers/utils.py index e195711c53b..de8ced7eb71 100644 --- a/neutron/pecan_wsgi/controllers/utils.py +++ b/neutron/pecan_wsgi/controllers/utils.py @@ -98,10 +98,11 @@ class NeutronPecanController(object): def __init__(self, collection, resource, plugin=None, resource_info=None, allow_pagination=None, allow_sorting=None, - parent_resource=None): + parent_resource=None, member_actions=None): # Ensure dashes are always replaced with underscores self.collection = collection and collection.replace('-', '_') self.resource = resource and resource.replace('-', '_') + self._member_actions = member_actions or {} self._resource_info = resource_info self._plugin = plugin # Controllers for some resources that are not mapped to anything in diff --git a/neutron/pecan_wsgi/hooks/notifier.py b/neutron/pecan_wsgi/hooks/notifier.py index 96b42409478..4277a40261b 100644 --- a/neutron/pecan_wsgi/hooks/notifier.py +++ b/neutron/pecan_wsgi/hooks/notifier.py @@ -23,6 +23,7 @@ from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.common import rpc as n_rpc from neutron import manager from neutron.pecan_wsgi import constants as pecan_constants +from neutron.pecan_wsgi.hooks import utils LOG = log.getLogger(__name__) @@ -66,6 +67,8 @@ class NotifierHook(hooks.PecanHook): resource = state.request.context.get('resource') if not resource: return + if utils.is_member_action(utils.get_controller(state)): + return action = pecan_constants.ACTION_MAP.get(state.request.method) event = '%s.%s.start' % (resource, action) if action in ('create', 'update'): @@ -92,6 +95,8 @@ class NotifierHook(hooks.PecanHook): if not action or action == 'get': LOG.debug("No notification will be sent for action: %s", action) return + if utils.is_member_action(utils.get_controller(state)): + return if state.response.status_int > 300: LOG.debug("No notification will be sent due to unsuccessful " "status code: %s", state.response.status_int) diff --git a/neutron/pecan_wsgi/hooks/policy_enforcement.py b/neutron/pecan_wsgi/hooks/policy_enforcement.py index 60e97f8a30f..4b6642f5beb 100644 --- a/neutron/pecan_wsgi/hooks/policy_enforcement.py +++ b/neutron/pecan_wsgi/hooks/policy_enforcement.py @@ -26,6 +26,7 @@ from neutron.extensions import quotasv2 from neutron import manager from neutron.pecan_wsgi import constants as pecan_constants from neutron.pecan_wsgi.controllers import quota +from neutron.pecan_wsgi.hooks import utils from neutron import policy @@ -35,10 +36,8 @@ def _custom_getter(resource, resource_id): return quota.get_tenant_quotas(resource_id)[quotasv2.RESOURCE_NAME] -def fetch_resource(neutron_context, collection, resource, resource_id, +def fetch_resource(neutron_context, controller, resource, resource_id, parent_id=None): - controller = manager.NeutronManager.get_controller_for_resource( - collection) attrs = controller.resource_info if not attrs: # this isn't a request for a normal resource. it could be @@ -51,7 +50,10 @@ def fetch_resource(neutron_context, collection, resource, resource_id, value.get('primary_key') or 'default' not in value)] plugin = manager.NeutronManager.get_plugin_for_resource(resource) if plugin: - getter = controller.plugin_shower + if utils.is_member_action(controller): + getter = controller.parent_controller.plugin_shower + else: + getter = controller.plugin_shower getter_args = [neutron_context, resource_id] if parent_id: getter_args.append(parent_id) @@ -80,15 +82,14 @@ class PolicyHook(hooks.PecanHook): # policies if not resource: return + controller = utils.get_controller(state) + if not controller or utils.is_member_action(controller): + return collection = state.request.context.get('collection') needs_prefetch = (state.request.method == 'PUT' or state.request.method == 'DELETE') policy.init() - # NOTE(tonytan4ever): needs to get the actual action from controller's - # _plugin_handlers - controller = manager.NeutronManager.get_controller_for_resource( - collection) action = controller.plugin_handlers[ pecan_constants.ACTION_MAP[state.request.method]] @@ -106,7 +107,7 @@ class PolicyHook(hooks.PecanHook): item = {} resource_id = state.request.context.get('resource_id') parent_id = state.request.context.get('parent_id') - resource_obj = fetch_resource(neutron_context, collection, + resource_obj = fetch_resource(neutron_context, controller, resource, resource_id, parent_id=parent_id) if resource_obj: @@ -141,6 +142,7 @@ class PolicyHook(hooks.PecanHook): neutron_context = state.request.context.get('neutron_context') resource = state.request.context.get('resource') collection = state.request.context.get('collection') + controller = utils.get_controller(state) if not resource: # can't filter a resource we don't recognize return @@ -165,8 +167,8 @@ class PolicyHook(hooks.PecanHook): policy_method = policy.enforce if is_single else policy.check plugin = manager.NeutronManager.get_plugin_for_resource(resource) try: - resp = [self._get_filtered_item(state.request, resource, - collection, item) + resp = [self._get_filtered_item(state.request, controller, + resource, collection, item) for item in to_process if (state.request.method != 'GET' or policy_method(neutron_context, action, item, @@ -182,10 +184,11 @@ class PolicyHook(hooks.PecanHook): resp = resp[0] state.response.json = {key: resp} - def _get_filtered_item(self, request, resource, collection, data): + def _get_filtered_item(self, request, controller, resource, collection, + data): neutron_context = request.context.get('neutron_context') to_exclude = self._exclude_attributes_by_policy( - neutron_context, resource, collection, data) + neutron_context, controller, resource, collection, data) return self._filter_attributes(request, data, to_exclude) def _filter_attributes(self, request, data, fields_to_strip): @@ -197,7 +200,7 @@ class PolicyHook(hooks.PecanHook): if (item[0] not in fields_to_strip and (not user_fields or item[0] in user_fields))) - def _exclude_attributes_by_policy(self, context, resource, + def _exclude_attributes_by_policy(self, context, controller, resource, collection, data): """Identifies attributes to exclude according to authZ policies. @@ -207,8 +210,6 @@ class PolicyHook(hooks.PecanHook): """ attributes_to_exclude = [] for attr_name in data.keys(): - controller = manager.NeutronManager.get_controller_for_resource( - collection) attr_data = controller.resource_info.get(attr_name) if attr_data and attr_data['is_visible']: if policy.check( diff --git a/neutron/pecan_wsgi/hooks/query_parameters.py b/neutron/pecan_wsgi/hooks/query_parameters.py index 82c9316cbfd..6cf63cc7096 100644 --- a/neutron/pecan_wsgi/hooks/query_parameters.py +++ b/neutron/pecan_wsgi/hooks/query_parameters.py @@ -15,6 +15,7 @@ from pecan import hooks from neutron.api import api_common from neutron import manager from neutron.pecan_wsgi.hooks import policy_enforcement +from neutron.pecan_wsgi.hooks import utils # TODO(blogan): ideally it'd be nice to get the pagination and sorting @@ -93,8 +94,7 @@ class QueryParametersHook(hooks.PecanHook): collection = state.request.context.get('collection') if not collection: return - controller = manager.NeutronManager.get_controller_for_resource( - collection) + controller = utils.get_controller(state) combined_fields, added_fields = _set_fields(state, controller) filters = _set_filters(state, controller) query_params = {'fields': combined_fields, 'filters': filters} diff --git a/neutron/pecan_wsgi/hooks/utils.py b/neutron/pecan_wsgi/hooks/utils.py new file mode 100644 index 00000000000..396aea0242b --- /dev/null +++ b/neutron/pecan_wsgi/hooks/utils.py @@ -0,0 +1,30 @@ +# Copyright (c) 2015 Taturiello Consulting, Meh. +# 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 neutron.pecan_wsgi.controllers import resource +from neutron.pecan_wsgi.controllers import utils as controller_utils + + +def get_controller(state): + if (state.arguments and state.arguments.args and + isinstance(state.arguments.args[0], + controller_utils.NeutronPecanController)): + controller = state.arguments.args[0] + return controller + + +def is_member_action(controller): + return isinstance(controller, + resource.MemberActionController) diff --git a/neutron/pecan_wsgi/startup.py b/neutron/pecan_wsgi/startup.py index a98cda37276..34287419f4f 100644 --- a/neutron/pecan_wsgi/startup.py +++ b/neutron/pecan_wsgi/startup.py @@ -70,6 +70,7 @@ def initialize_all(): resource = legacy_controller.resource 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', @@ -80,7 +81,7 @@ def initialize_all(): parent_resource = parent.get('member_name') new_controller = res_ctrl.CollectionsController( collection, resource, resource_info=attr_info, - parent_resource=parent_resource) + parent_resource=parent_resource, member_actions=member_actions) manager.NeutronManager.set_plugin_for_resource(resource, plugin) if path_prefix: manager.NeutronManager.add_resource_for_path_prefix( diff --git a/neutron/tests/functional/pecan_wsgi/test_controllers.py b/neutron/tests/functional/pecan_wsgi/test_controllers.py index c00ff7a8044..43dc4bb5f97 100644 --- a/neutron/tests/functional/pecan_wsgi/test_controllers.py +++ b/neutron/tests/functional/pecan_wsgi/test_controllers.py @@ -800,3 +800,51 @@ class TestShimControllers(test_functional.PecanFunctionalTest): self.assertEqual(200, resp.status_int) self.assertEqual({sub_resource_collection: {'foo': temp_id}}, resp.json) + + +class TestMemberActionController(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(TestMemberActionController, self).setUp( + service_plugins=plugins, extensions=new_extensions) + hyphen_collection = pecan_utils.FakeExtension.HYPHENATED_COLLECTION + self.collection = hyphen_collection.replace('_', '-') + + def test_get_member_action_controller(self): + url = '/v2.0/{}/something/boo_meh.json'.format(self.collection) + resp = self.app.get(url) + self.assertEqual(200, resp.status_int) + self.assertEqual({'boo_yah': 'something'}, resp.json) + + def test_put_member_action_controller(self): + url = '/v2.0/{}/something/put_meh.json'.format(self.collection) + resp = self.app.put_json(url, params={'it_matters_not': 'ok'}) + self.assertEqual(200, resp.status_int) + self.assertEqual({'poo_yah': 'something'}, resp.json) + + def test_get_member_action_does_not_exist(self): + url = '/v2.0/{}/something/are_you_still_there.json'.format( + self.collection) + resp = self.app.get(url, expect_errors=True) + self.assertEqual(404, resp.status_int) + + def test_put_member_action_does_not_exist(self): + url = '/v2.0/{}/something/are_you_still_there.json'.format( + self.collection) + resp = self.app.put_json(url, params={'it_matters_not': 'ok'}, + expect_errors=True) + self.assertEqual(404, resp.status_int) + + def test_put_on_get_member_action(self): + url = '/v2.0/{}/something/boo_meh.json'.format(self.collection) + resp = self.app.put_json(url, params={'it_matters_not': 'ok'}, + expect_errors=True) + self.assertEqual(405, resp.status_int) + + def test_get_on_put_member_action(self): + url = '/v2.0/{}/something/put_meh.json'.format(self.collection) + resp = self.app.get(url, expect_errors=True) + self.assertEqual(405, resp.status_int) diff --git a/neutron/tests/functional/pecan_wsgi/test_hooks.py b/neutron/tests/functional/pecan_wsgi/test_hooks.py index 10499ff39b2..eea01b5796c 100644 --- a/neutron/tests/functional/pecan_wsgi/test_hooks.py +++ b/neutron/tests/functional/pecan_wsgi/test_hooks.py @@ -335,7 +335,9 @@ class TestNovaNotifierHook(test_functional.PecanFunctionalTest): # NOTE(kevinbenton): the original passed into the notifier does # not contain all of the fields of the object. Only those required # by the policy engine are included. - orig = pe.fetch_resource(context.get_admin_context(), 'networks', + controller = manager.NeutronManager.get_controller_for_resource( + 'networks') + orig = pe.fetch_resource(context.get_admin_context(), controller, 'network', network_id) response = self.app.put_json( '/v2.0/networks/%s.json' % network_id, @@ -347,7 +349,7 @@ class TestNovaNotifierHook(test_functional.PecanFunctionalTest): orig, json_body) self.mock_notifier.reset_mock() - orig = pe.fetch_resource(context.get_admin_context(), 'networks', + orig = pe.fetch_resource(context.get_admin_context(), controller, 'network', network_id) response = self.app.delete( '/v2.0/networks/%s.json' % network_id, headers=req_headers) diff --git a/neutron/tests/functional/pecan_wsgi/utils.py b/neutron/tests/functional/pecan_wsgi/utils.py index c9b0e01968f..8bd0c240712 100644 --- a/neutron/tests/functional/pecan_wsgi/utils.py +++ b/neutron/tests/functional/pecan_wsgi/utils.py @@ -150,14 +150,15 @@ class FakeExtension(extensions.ExtensionDescriptor): params = self.RAM.get(self.HYPHENATED_COLLECTION, {}) attributes.PLURALS.update({self.HYPHENATED_COLLECTION: self.HYPHENATED_RESOURCE}) + member_actions = {'put_meh': 'PUT', 'boo_meh': 'GET'} fake_plugin = FakePlugin() controller = base.create_resource( collection, self.HYPHENATED_RESOURCE, FakePlugin(), params, allow_bulk=True, allow_pagination=True, - allow_sorting=True) - resources = [extensions.ResourceExtension(collection, - controller, - attr_map=params)] + allow_sorting=True, member_actions=member_actions) + resources = [extensions.ResourceExtension( + collection, controller, attr_map=params, + member_actions=member_actions)] for collection_name in self.SUB_RESOURCE_ATTRIBUTE_MAP: resource_name = collection_name parent = self.SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get( @@ -204,3 +205,9 @@ class FakePlugin(object): def get_meh_meh_fake_subresources(self, context, id_, fields=None, filters=None): return {'foo': id_} + + def put_meh(self, context, id_, data): + return {'poo_yah': id_} + + def boo_meh(self, context, id_, fields=None): + return {'boo_yah': id_}