app_hooks = [
hooks.ExceptionTranslationHook(), # priority 100
hooks.ContextHook(), # priority 95
+ hooks.ResourceIdentifierHook(), # priority 95
]
app = pecan.make_app(
# 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
--- /dev/null
+# 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)
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)
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'])