]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add resource/plugin identification hook
authorKevin Benton <blak111@gmail.com>
Thu, 11 Jun 2015 12:17:49 +0000 (05:17 -0700)
committerKevin Benton <kevinbenton@buttewifi.com>
Sat, 1 Aug 2015 18:55:36 +0000 (18:55 +0000)
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

neutron/newapi/app.py
neutron/newapi/hooks/__init__.py
neutron/newapi/hooks/resource_identifier.py [new file with mode: 0644]
neutron/tests/functional/newapi/test_functional.py

index fc59ba26dc9dc870e0bc8f743a53c5f30ed4a565..b8f7e6bb3a9e7444692d088cbd42a0d887de304c 100644 (file)
@@ -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(
index eaffa81287d48458821fe6f99baddf3f5be9cb75..69bf31cd91e1e60abed7ca24065586d4a4d0bdcb 100644 (file)
 #    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 (file)
index 0000000..5b6f1ba
--- /dev/null
@@ -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)
index 8a57fb007779ca68a83d608b803c3d5cf9ab953b..818f38c55bc7e95521cc0a176ae142512b868790 100644 (file)
@@ -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'])