From: Kevin Benton Date: Thu, 11 Jun 2015 12:17:49 +0000 (-0700) Subject: Add resource/plugin identification hook X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=9c1a3858c7163ce84f299dffabaf2b0f370ee466;p=openstack-build%2Fneutron-build.git Add resource/plugin identification hook Adds a hook that identifies the resource type and identifies the associated plugin (core or service). Both are placed into the request context for use by other hooks and the controller. Partially-Implements: blueprint wsgi-pecan-switch Change-Id: I90854c240e6e40e429d00945d7db8e96923f83f7 --- diff --git a/neutron/newapi/app.py b/neutron/newapi/app.py index fc59ba26d..b8f7e6bb3 100644 --- a/neutron/newapi/app.py +++ b/neutron/newapi/app.py @@ -44,6 +44,7 @@ def setup_app(*args, **kwargs): app_hooks = [ hooks.ExceptionTranslationHook(), # priority 100 hooks.ContextHook(), # priority 95 + hooks.ResourceIdentifierHook(), # priority 95 ] app = pecan.make_app( diff --git a/neutron/newapi/hooks/__init__.py b/neutron/newapi/hooks/__init__.py index eaffa8128..69bf31cd9 100644 --- a/neutron/newapi/hooks/__init__.py +++ b/neutron/newapi/hooks/__init__.py @@ -14,8 +14,10 @@ # under the License. from neutron.newapi.hooks import context +from neutron.newapi.hooks import resource_identifier from neutron.newapi.hooks import translation ExceptionTranslationHook = translation.ExceptionTranslationHook ContextHook = context.ContextHook +ResourceIdentifierHook = resource_identifier.ResourceIdentifierHook diff --git a/neutron/newapi/hooks/resource_identifier.py b/neutron/newapi/hooks/resource_identifier.py new file mode 100644 index 000000000..5b6f1bade --- /dev/null +++ b/neutron/newapi/hooks/resource_identifier.py @@ -0,0 +1,64 @@ +# Copyright (c) 2015 Mirantis, Inc. +# 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 pecan import abort +from pecan import hooks + +from neutron.api import extensions +from neutron.api.v2 import attributes +from neutron.api.v2 import router + +from neutron import manager + + +class ResourceIdentifierHook(hooks.PecanHook): + + priority = 95 + + def before(self, state): + # TODO(kevinbenton): find a better way to look this up. maybe something + # in the pecan internals somewhere? + state.request.resource_type = None + try: + url_type = state.request.path.split('/')[2].rsplit('.', 1)[0] + except IndexError: + return + + for plural, single in attributes.PLURALS.items(): + if plural == url_type: + state.request.resource_type = single + state.request.plugin = self._plugin_for_resource(single) + return + abort(404, detail='Resource: %s' % url_type) + + def _plugin_for_resource(self, resource): + # NOTE(kevinbenton): memoizing the responses to this had no useful + # performance improvement so I avoided it to keep complexity and + # risks of memory leaks low. + if resource in router.RESOURCES: + # this is a core resource, return the core plugin + return manager.NeutronManager.get_plugin() + try: + ext_mgr = extensions.PluginAwareExtensionManager.get_instance() + # find the plugin that supports this extension + # TODO(kevinbenton): fix this. it incorrectly assumes the alias + # matches the resource. need to walk extensions and build map + for plugin in ext_mgr.plugins.values(): + if (hasattr(plugin, 'supported_extension_aliases') and + resource in plugin.supported_extension_aliases): + return plugin + except KeyError: + pass + abort(404, detail='Resource: %s' % resource) diff --git a/neutron/tests/functional/newapi/test_functional.py b/neutron/tests/functional/newapi/test_functional.py index 8a57fb007..818f38c55 100644 --- a/neutron/tests/functional/newapi/test_functional.py +++ b/neutron/tests/functional/newapi/test_functional.py @@ -78,7 +78,7 @@ class TestErrors(PecanFunctionalTest): self.assertEqual(response.status_int, 404) def test_bad_method(self): - response = self.app.patch('/v2.0/', + response = self.app.patch('/v2.0/ports/44.json', expect_errors=True) self.assertEqual(response.status_int, 405) @@ -136,19 +136,46 @@ class TestExceptionTranslationHook(PecanFunctionalTest): self.assertEqual(response.status_int, 500) -class TestContextHook(PecanFunctionalTest): +class TestRequestPopulatingHooks(PecanFunctionalTest): - # TODO(kevinbenton): add tests for X-Roles etc + def setUp(self): + super(TestRequestPopulatingHooks, self).setUp() - def test_context_set_in_request(self): - request_stash = [] # request.context is thread-local storage so it has to be accessed by # the controller. We can capture it into a list here to assert on after # the request finishes. - with mock.patch( + + def capture_request_details(*args, **kwargs): + self.req_stash = { + 'context': request.context, + 'resource_type': request.resource_type, + 'plugin': request.plugin + } + mock.patch( 'neutron.newapi.controllers.root.GeneralController.get', - side_effect=lambda *x, **y: request_stash.append(request.context) - ): - self.app.get('/v2.0/ports.json', - headers={'X-Tenant-Id': 'tenant_id'}) - self.assertEqual('tenant_id', request_stash[0].tenant_id) + side_effect=capture_request_details + ).start() + + # TODO(kevinbenton): add context tests for X-Roles etc + + def test_context_set_in_request(self): + self.app.get('/v2.0/ports.json', + headers={'X-Tenant-Id': 'tenant_id'}) + self.assertEqual('tenant_id', self.req_stash['context'].tenant_id) + + def test_core_resource_identified(self): + self.app.get('/v2.0/ports.json') + self.assertEqual('port', self.req_stash['resource_type']) + # make sure the core plugin was identified as the handler for ports + self.assertEqual(manager.NeutronManager.get_plugin(), + self.req_stash['plugin']) + + def test_service_plugin_identified(self): + # TODO(kevinbenton): fix the unit test setup to include an l3 plugin + self.skipTest("A dummy l3 plugin needs to be setup") + self.app.get('/v2.0/routers.json') + self.assertEqual('router', self.req_stash['resource_type']) + # make sure the core plugin was identified as the handler for ports + self.assertEqual( + manager.NeutronManager.get_service_plugins()['L3_ROUTER_NAT'], + self.req_stash['plugin'])