Use pecan controllers for routing

Pecan defines several efficient mechanism for routing requests to
the appropriate controller, but the current code for Neutron's
Pecan WSGI server basically uses Pecan hooks to route requests.

This patch partially fixes that, removing the 'resource_identifier'
pecan hook and replacing it with explicit pecan routes between
controllers added at resource registration time.

All the remaining hooks, like attribute_population and
policy_enforments, which were relying on finding the resource
name in the pecan.request threadlocal variable have been updated.

This patch also:
- ensures the appropriate plugin is always selected for a given
  resource
- add a common NeutronPecanController base class for the classes
  CollectionsController and ItemaController
- Fixes the way in which plurals and singulars are handled in
  neutron.api.v2.resource_heper

Change-Id: I4ec0d2276c3974117b497228d289c3fb0dc5a140
This commit is contained in:
Salvatore Orlando 2015-08-14 16:32:38 -07:00
parent 40151be9b3
commit 2c40310584
14 changed files with 310 additions and 91 deletions

View File

@ -601,6 +601,10 @@ class PluginAwareExtensionManager(ExtensionManager):
pass
return aliases
@classmethod
def clear_instance(cls):
cls._instance = None
def check_if_plugin_extensions_loaded(self):
"""Check if an extension supported by a plugin has been loaded."""
plugin_extensions = self.get_supported_extension_aliases()

View File

@ -28,13 +28,19 @@ LOG = logging.getLogger(__name__)
def build_plural_mappings(special_mappings, resource_map):
"""Create plural to singular mapping for all resources.
Allows for special mappings to be provided, like policies -> policy.
Allows for special mappings to be provided, for particular cases..
Otherwise, will strip off the last character for normal mappings, like
routers -> router.
routers -> router, unless the plural name ends with 'ies', in which
case the singular form will end with a 'y' (e.g.: policy/policies)
"""
plural_mappings = {}
for plural in resource_map:
singular = special_mappings.get(plural, plural[:-1])
singular = special_mappings.get(plural)
if not singular:
if plural.endswith('ies'):
singular = "%sy" % plural[:-3]
else:
singular = plural[:-1]
plural_mappings[plural] = singular
return plural_mappings

View File

@ -129,6 +129,9 @@ class NeutronManager(object):
# the rest of service plugins
self.service_plugins = {constants.CORE: self.plugin}
self._load_service_plugins()
# Used by pecan WSGI
self.resource_plugin_mappings = {}
self.resource_controller_mappings = {}
@staticmethod
def load_class_for_provider(namespace, plugin_provider):
@ -251,3 +254,19 @@ class NeutronManager(object):
def get_unique_service_plugins(cls):
service_plugins = cls.get_instance().service_plugins
return tuple(weakref.proxy(x) for x in set(service_plugins.values()))
@classmethod
def set_plugin_for_resource(cls, resource, plugin):
cls.get_instance().resource_plugin_mappings[resource] = plugin
@classmethod
def get_plugin_for_resource(cls, resource):
return cls.get_instance().resource_plugin_mappings.get(resource)
@classmethod
def set_controller_for_resource(cls, resource, controller):
cls.get_instance().resource_controller_mappings[resource] = controller
@classmethod
def get_controller_for_resource(cls, resource):
return cls.get_instance().resource_controller_mappings.get(resource)

View File

@ -44,7 +44,7 @@ def setup_app(*args, **kwargs):
app_hooks = [
hooks.ExceptionTranslationHook(), # priority 100
hooks.ContextHook(), # priority 95
hooks.ResourceIdentifierHook(), # priority 95
hooks.MemberActionHook(), # piority 95
hooks.AttributePopulationHook(), # priority 120
hooks.OwnershipValidationHook(), # priority 125
hooks.QuotaEnforcementHook(), # priority 130

View File

@ -14,12 +14,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log
import pecan
from pecan import request
from neutron.api import extensions
from neutron.api.views import versions as versions_view
from neutron.i18n import _LW
from neutron import manager
LOG = log.getLogger(__name__)
_VERSION_INFO = {}
@ -99,8 +103,17 @@ class V2Controller(object):
pecan.abort(405)
@expose()
def _lookup(self, endpoint, *remainder):
return CollectionsController(endpoint), remainder
def _lookup(self, collection, *remainder):
controller = manager.NeutronManager.get_controller_for_resource(
collection)
if not controller:
LOG.warn(_LW("No controller found for: %s - returning response "
"code 404"), collection)
pecan.abort(404)
# Store resource name in pecan request context so that hooks can
# leverage it if necessary
request.context['resource'] = controller.resource
return controller, remainder
# This controller cannot be specified directly as a member of RootController
@ -124,14 +137,20 @@ class ExtensionController(object):
return {'extension': extensions.ExtensionController._translate(ext)}
class CollectionsController(object):
class NeutronPecanController(object):
def __init__(self, collection):
def __init__(self, collection, resource):
self.collection = collection
self.resource = resource
self.plugin = manager.NeutronManager.get_plugin_for_resource(
self.resource)
class CollectionsController(NeutronPecanController):
@expose()
def _lookup(self, item, *remainder):
return ItemController(item), remainder
return ItemController(self.resource, item), remainder
@expose(generic=True)
def index(self, *args, **kwargs):
@ -145,25 +164,28 @@ class CollectionsController(object):
_listify = lambda x: x if isinstance(x, list) else [x]
filters = {k: _listify(v) for k, v in kwargs.items()}
# TODO(kevinbenton): convert these using api_common.get_filters
lister = getattr(request.plugin, 'get_%s' % self.collection)
return {self.collection: lister(request.context, filters=filters)}
lister = getattr(self.plugin, 'get_%s' % self.collection)
neutron_context = request.context.get('neutron_context')
return {self.collection: lister(neutron_context, filters=filters)}
@when(index, method='POST')
def post(self, *args, **kwargs):
# TODO(kevinbenton): emulated bulk!
pecan.response.status = 201
if request.bulk:
method = 'create_%s_bulk' % request.resource_type
method = 'create_%s_bulk' % self.resource
else:
method = 'create_%s' % request.resource_type
creator = getattr(request.plugin, method)
key = self.collection if request.bulk else request.resource_type
return {key: creator(request.context, request.prepared_data)}
method = 'create_%s' % self.resource
creator = getattr(self.plugin, method)
key = self.collection if request.bulk else self.resource
neutron_context = request.context.get('neutron_context')
return {key: creator(neutron_context, request.prepared_data)}
class ItemController(object):
class ItemController(NeutronPecanController):
def __init__(self, item):
def __init__(self, resource, item):
super(ItemController, self).__init__(None, resource)
self.item = item
@expose(generic=True)
@ -171,23 +193,26 @@ class ItemController(object):
return self.get()
def get(self, *args, **kwargs):
getter = getattr(request.plugin, 'get_%s' % request.resource_type)
return {request.resource_type: getter(request.context, self.item)}
getter = getattr(self.plugin, 'get_%s' % self.resource)
neutron_context = request.context.get('neutron_context')
return {self.resource: getter(neutron_context, self.item)}
@when(index, method='PUT')
def put(self, *args, **kwargs):
neutron_context = request.context.get('neutron_context')
if request.member_action:
member_action_method = getattr(request.plugin,
member_action_method = getattr(self.plugin,
request.member_action)
return member_action_method(request.context, self.item,
return member_action_method(neutron_context, self.item,
request.prepared_data)
updater = getattr(request.plugin, 'update_%s' % request.resource_type)
return {request.resource_type: updater(
request.context, self.item, request.prepared_data)}
# TODO(kevinbenton): bulk?
updater = getattr(self.plugin, 'update_%s' % self.resource)
return updater(neutron_context, self.item, request.prepared_data)
@when(index, method='DELETE')
def delete(self):
# TODO(kevinbenton): setting code could be in a decorator
pecan.response.status = 204
deleter = getattr(request.plugin, 'delete_%s' % request.resource_type)
return deleter(request.context, self.item)
neutron_context = request.context.get('neutron_context')
deleter = getattr(self.plugin, 'delete_%s' % self.resource)
return deleter(neutron_context, self.item)

View File

@ -15,17 +15,17 @@
from neutron.pecan_wsgi.hooks import attribute_population
from neutron.pecan_wsgi.hooks import context
from neutron.pecan_wsgi.hooks import member_action
from neutron.pecan_wsgi.hooks import notifier
from neutron.pecan_wsgi.hooks import ownership_validation
from neutron.pecan_wsgi.hooks import policy_enforcement
from neutron.pecan_wsgi.hooks import quota_enforcement
from neutron.pecan_wsgi.hooks import resource_identifier
from neutron.pecan_wsgi.hooks import translation
ExceptionTranslationHook = translation.ExceptionTranslationHook
ContextHook = context.ContextHook
ResourceIdentifierHook = resource_identifier.ResourceIdentifierHook
MemberActionHook = member_action.MemberActionHook
AttributePopulationHook = attribute_population.AttributePopulationHook
OwnershipValidationHook = ownership_validation.OwnershipValidationHook
PolicyHook = policy_enforcement.PolicyHook

View File

@ -17,6 +17,7 @@ from pecan import hooks
from neutron.api.v2 import attributes
from neutron.api.v2 import base as v2base
from neutron import manager
class AttributePopulationHook(hooks.PecanHook):
@ -29,7 +30,8 @@ class AttributePopulationHook(hooks.PecanHook):
if state.request.method not in ('POST', 'PUT'):
return
is_create = state.request.method == 'POST'
resource = state.request.resource_type
resource = state.request.context.get('resource')
neutron_context = state.request.context['neutron_context']
if not resource:
return
if state.request.member_action:
@ -41,23 +43,24 @@ class AttributePopulationHook(hooks.PecanHook):
else:
state.request.prepared_data = (
v2base.Controller.prepare_request_body(
state.request.context, state.request.json, is_create,
neutron_context, state.request.json, is_create,
resource, _attributes_for_resource(resource),
allow_bulk=True))
# TODO(kevinbenton): conditional allow_bulk
state.request.resources = _extract_resources_from_state(state)
# make the original object available:
if not is_create and not state.request.member_action:
obj_id = _pull_id_from_request(state.request)
obj_id = _pull_id_from_request(state.request, resource)
attrs = _attributes_for_resource(resource)
field_list = [name for (name, value) in attrs.items()
if (value.get('required_by_policy') or
value.get('primary_key') or
'default' not in value)]
plugin = state.request.plugin
plugin = manager.NeutronManager.get_plugin_for_resource(resource)
getter = getattr(plugin, 'get_%s' % resource)
# TODO(kevinbenton): the parent_id logic currently in base.py
obj = getter(state.request.context, obj_id, fields=field_list)
obj = getter(neutron_context, obj_id, fields=field_list)
state.request.original_object = obj
@ -68,11 +71,11 @@ def _attributes_for_resource(resource):
_plural(resource), {})
def _pull_id_from_request(request):
def _pull_id_from_request(request, resource):
# NOTE(kevinbenton): this sucks
# Converting /v2.0/ports/dbbdae29-82f6-49cf-b05e-3365bcc95b7a.json
# into dbbdae29-82f6-49cf-b05e-3365bcc95b7a
resources = _plural(request.resource_type)
resources = _plural(resource)
jsontrail = request.path_info.replace('/v2.0/%s/' % resources, '')
obj_id = jsontrail.replace('.json', '')
return obj_id
@ -85,17 +88,17 @@ def _plural(rtype):
def _extract_resources_from_state(state):
resource_type = state.request.resource_type
if not resource_type:
resource = state.request.context['resource']
if not resource:
return []
data = state.request.prepared_data
# single item
if resource_type in data:
if resource in data:
state.request.bulk = False
return [data[resource_type]]
return [data[resource]]
# multiple items
if _plural(resource_type) in data:
if _plural(resource) in data:
state.request.bulk = True
return [x[resource_type] for x in data[_plural(resource_type)]]
return data[_plural(resource)]
return []

View File

@ -55,4 +55,4 @@ class ContextHook(hooks.PecanHook):
request_id=req_id, auth_token=auth_token)
# Inject the context...
state.request.context = ctx
state.request.context['neutron_context'] = ctx

View File

@ -0,0 +1,68 @@
# 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
class MemberActionHook(hooks.PecanHook):
priority = 95
def before(self, state):
# TODO(salv-orlando): This hook must go. Handling actions like this is
# shameful
resource = state.request.context.get('resource')
if not resource:
return
try:
# Remove the format suffix if any
uri = state.request.path.rsplit('.', 1)[0].split('/')[2:]
if not uri:
# there's nothing to process in the URI
return
except IndexError:
return
collection = None
for (collection, res) in attributes.PLURALS.items():
if res == resource:
break
else:
return
state.request.member_action = self._parse_action(
resource, collection, uri[1:])
def _parse_action(self, resource, collection, remainder):
# NOTE(salv-orlando): This check is revolting and makes me
# puke, but avoids silly failures when dealing with API actions
# such as "add_router_interface".
if len(remainder) > 1:
action = remainder[1]
else:
return
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
resource_exts = ext_mgr.get_resources()
for ext in resource_exts:
if (ext.collection == collection and action in ext.member_actions):
return action
# Action or resource extension not found
if action:
abort(404, detail="Action %(action)s for resource "
"%(resource)s undefined" %
{'action': action,
'resource': resource})

View File

@ -28,17 +28,18 @@ class OwnershipValidationHook(hooks.PecanHook):
return
items = state.request.resources
for item in items:
self._validate_network_tenant_ownership(state.request, item)
self._validate_network_tenant_ownership(state, item)
def _validate_network_tenant_ownership(self, request, resource_item):
def _validate_network_tenant_ownership(self, state, resource_item):
# TODO(salvatore-orlando): consider whether this check can be folded
# in the policy engine
rtype = request.resource_type
if (request.context.is_admin or request.context.is_advsvc or
rtype not in ('port', 'subnet')):
neutron_context = state.request.context.get('neutron_context')
resource = state.request.context.get('resource')
if (neutron_context.is_admin or neutron_context.is_advsvc or
resource not in ('port', 'subnet')):
return
plugin = manager.NeutronManager.get_plugin()
network = plugin.get_network(request.context,
network = plugin.get_network(neutron_context,
resource_item['network_id'])
# do not perform the check on shared networks
if network.get('shared'):
@ -51,5 +52,5 @@ class OwnershipValidationHook(hooks.PecanHook):
"create %(resource)s on this network")
raise webob.exc.HTTPForbidden(msg % {
"tenant_id": resource_item['tenant_id'],
"resource": rtype,
"resource": resource,
})

View File

@ -16,6 +16,7 @@
import copy
import simplejson
from oslo_log import log
from oslo_policy import policy as oslo_policy
from oslo_utils import excutils
import pecan
@ -23,9 +24,12 @@ from pecan import hooks
import webob
from neutron.common import constants as const
from neutron import manager
from neutron.pecan_wsgi.hooks import attribute_population
from neutron import policy
LOG = log.getLogger(__name__)
class PolicyHook(hooks.PecanHook):
priority = 135
@ -35,13 +39,12 @@ class PolicyHook(hooks.PecanHook):
def before(self, state):
if state.request.method not in self.ACTION_MAP:
pecan.abort(405)
rtype = state.request.resource_type
if not rtype:
return
neutron_context = state.request.context.get('neutron_context')
resource = state.request.context.get('resource')
is_update = (state.request.method == 'PUT')
items = state.request.resources
policy.init()
action = '%s_%s' % (self.ACTION_MAP[state.request.method], rtype)
action = '%s_%s' % (self.ACTION_MAP[state.request.method], resource)
for item in items:
if is_update:
obj = copy.copy(state.request.original_object)
@ -49,57 +52,60 @@ class PolicyHook(hooks.PecanHook):
obj[const.ATTRIBUTES_TO_UPDATE] = item.keys()
item = obj
try:
policy.enforce(state.request.context, action, item,
pluralized=attribute_population._plural(rtype))
policy.enforce(
neutron_context, action, item,
pluralized=attribute_population._plural(resource))
except oslo_policy.PolicyNotAuthorized:
with excutils.save_and_reraise_exception() as ctxt:
# If a tenant is modifying it's own object, it's safe to
# return a 403. Otherwise, pretend that it doesn't exist
# to avoid giving away information.
context = state.request.context
if (is_update and
context.tenant_id != obj['tenant_id']):
neutron_context.tenant_id != obj['tenant_id']):
ctxt.reraise = False
msg = _('The resource could not be found.')
raise webob.exc.HTTPNotFound(msg)
def after(self, state):
resource_type = getattr(state.request, 'resource_type', None)
if not resource_type:
neutron_context = state.request.context.get('neutron_context')
resource = state.request.context.get('resource')
if not resource:
# can't filter a resource we don't recognize
return
# NOTE(kevinbenton): extension listing isn't controlled by policy
if resource_type == 'extension':
if resource == 'extension':
return
try:
data = state.response.json
except simplejson.JSONDecodeError:
return
action = '%s_%s' % (self.ACTION_MAP[state.request.method],
resource_type)
plural = attribute_population._plural(resource_type)
if not data or (resource_type not in data and plural not in data):
resource)
plural = attribute_population._plural(resource)
if not data or (resource not in data and plural not in data):
return
is_single = resource_type in data
key = resource_type if is_single else plural
to_process = [data[resource_type]] if is_single else data[plural]
is_single = resource in data
key = resource if is_single else plural
to_process = [data[resource]] if is_single else data[plural]
# in the single case, we enforce which raises on violation
# in the plural case, we just check so violating items are hidden
policy_method = policy.enforce if is_single else policy.check
resp = [self._get_filtered_item(state.request, resource_type, item)
plugin = manager.NeutronManager.get_plugin_for_resource(resource)
resp = [self._get_filtered_item(state.request, resource, item)
for item in to_process
if (state.request.method != 'GET' or
policy_method(state.request.context, action, item,
plugin=state.request.plugin,
policy_method(neutron_context, action, item,
plugin=plugin,
pluralized=plural))]
if is_single:
resp = resp[0]
data[key] = resp
state.response.json = data
def _get_filtered_item(self, request, resource_type, data):
to_exclude = self._exclude_attributes_by_policy(request.context,
resource_type, data)
def _get_filtered_item(self, request, resource, data):
neutron_context = request.context.get('neutron_context')
to_exclude = self._exclude_attributes_by_policy(
neutron_context, resource, data)
return self._filter_attributes(request, data, to_exclude)
def _filter_attributes(self, request, data, fields_to_strip):
@ -111,7 +117,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_type, data):
def _exclude_attributes_by_policy(self, context, resource, data):
"""Identifies attributes to exclude according to authZ policies.
Return a list of attribute names which should be stripped from the
@ -121,16 +127,16 @@ class PolicyHook(hooks.PecanHook):
attributes_to_exclude = []
for attr_name in data.keys():
attr_data = attribute_population._attributes_for_resource(
resource_type).get(attr_name)
resource).get(attr_name)
if attr_data and attr_data['is_visible']:
if policy.check(
context,
# NOTE(kevinbenton): this used to reference a
# _plugin_handlers dict, why?
'get_%s:%s' % (resource_type, attr_name),
'get_%s:%s' % (resource, attr_name),
data,
might_not_exist=True,
pluralized=attribute_population._plural(resource_type)):
pluralized=attribute_population._plural(resource)):
# this attribute is visible, check next one
continue
# if the code reaches this point then either the policy check

View File

@ -13,12 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.common import exceptions
from neutron import quota
from oslo_log import log as logging
from pecan import hooks
from neutron.common import exceptions
from neutron import manager
from neutron.pecan_wsgi.hooks import attribute_population
from neutron import quota
LOG = logging.getLogger(__name__)
@ -27,22 +30,29 @@ class QuotaEnforcementHook(hooks.PecanHook):
priority = 130
def before(self, state):
# TODO(salv-orlando): This hook must go when adaptin the pecan code to
# use reservations.
if state.request.method != 'POST':
return
resource = state.request.context.get('resource')
plugin = manager.NeutronManager.get_plugin_for_resource(resource)
items = state.request.resources
rtype = state.request.resource_type
deltas = {}
for item in items:
tenant_id = item['tenant_id']
try:
count = quota.QUOTAS.count(state.request.context, rtype,
state.request.plugin,
neutron_context = state.request.context.get('neutron_context')
count = quota.QUOTAS.count(neutron_context,
resource,
plugin,
attribute_population._plural(
resource),
tenant_id)
delta = deltas.get(tenant_id, 0) + 1
kwargs = {rtype: count + delta}
kwargs = {resource: count + delta}
except exceptions.QuotaResourceUnknown as e:
# We don't want to quota this resource
LOG.debug(e)
else:
quota.QUOTAS.limit_check(state.request.context, tenant_id,
quota.QUOTAS.limit_check(neutron_context, tenant_id,
**kwargs)

View File

@ -13,14 +13,93 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log
from neutron.api import extensions
from neutron.api.v2 import attributes
from neutron.api.v2 import router
from neutron.i18n import _LI, _LW
from neutron import manager
from neutron.pecan_wsgi.controllers import root
from neutron import policy
LOG = log.getLogger(__name__)
def _plugin_for_resource(collection):
if collection in router.RESOURCES.values():
# this is a core resource, return the core plugin
return manager.NeutronManager.get_plugin()
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
# Multiple extensions can map to the same resource. This happens
# because of 'attribute' extensions. Due to the way in which neutron
# plugins and request dispatching is constructed, it is impossible for
# the same resource to be handled by more than one plugin. Therefore
# all the extensions mapped to a given resource will necessarily be
# implemented by the same plugin.
ext_res_mappings = dict((ext.get_alias(), collection) for
ext in ext_mgr.extensions.values() if
collection in ext.get_extended_resources('2.0'))
LOG.debug("Extension mappings for: %(collection)s: %(aliases)s",
{'collection': collection, 'aliases': ext_res_mappings.keys()})
# find the plugin that supports this extension
for plugin in ext_mgr.plugins.values():
ext_aliases = getattr(plugin, 'supported_extension_aliases', [])
for alias in ext_aliases:
if alias in ext_res_mappings:
# This plugin implements this resource
return plugin
LOG.warn(_LW("No plugin found for:%s"), collection)
def _handle_plurals(collection):
resource = attributes.PLURALS.get(collection)
if not resource:
if collection.endswith('ies'):
resource = "%sy" % collection[:-3]
else:
resource = collection[:-1]
attributes.PLURALS[collection] = resource
return resource
def initialize_all():
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
# At this stage we have a fully populated resource attribute map;
# build Pecan controllers and routes for every resource (both core
# and extensions)
pecanized_exts = [ext for ext in ext_mgr.extensions.values() if
hasattr(ext, 'get_pecan_controllers')]
pecan_controllers = {}
for ext in pecanized_exts:
LOG.debug("Extension %s is pecan-enabled. Fetching resources "
"and controllers", ext.get_name())
controllers = ext.get_pecan_controllers()
# controllers is actually a list of pairs where the first element is
# the collection name and the second the actual controller
for (collection, coll_controller) in controllers:
pecan_controllers[collection] = coll_controller
for collection in attributes.RESOURCE_ATTRIBUTE_MAP:
if collection not in pecan_controllers:
resource = _handle_plurals(collection)
LOG.debug("Building controller for resource:%s", resource)
plugin = _plugin_for_resource(collection)
if plugin:
manager.NeutronManager.set_plugin_for_resource(
resource, plugin)
controller = root.CollectionsController(collection, resource)
manager.NeutronManager.set_controller_for_resource(
collection, controller)
LOG.info(_LI("Added controller for resource %(resource)s "
"via URI path segment:%(collection)s"),
{'resource': resource,
'collection': collection})
else:
LOG.debug("There are already controllers for resource:%s",
resource)
for ext in ext_mgr.extensions.values():
# make each extension populate its plurals
if hasattr(ext, 'get_resources'):

View File

@ -24,6 +24,7 @@ from pecan import set_config
from pecan.testing import load_test_app
import testtools
from neutron.api import extensions
from neutron.api.v2 import attributes
from neutron.common import exceptions as n_exc
from neutron import context
@ -37,6 +38,7 @@ class PecanFunctionalTest(testlib_api.SqlTestCase):
def setUp(self):
self.setup_coreplugin('neutron.plugins.ml2.plugin.Ml2Plugin')
super(PecanFunctionalTest, self).setUp()
self.addCleanup(extensions.PluginAwareExtensionManager.clear_instance)
self.addCleanup(set_config, {}, overwrite=True)
self.set_config_overrides()
self.setup_app()
@ -181,9 +183,8 @@ class TestRequestPopulatingHooks(PecanFunctionalTest):
def capture_request_details(*args, **kwargs):
self.req_stash = {
'context': request.context,
'resource_type': request.resource_type,
'plugin': request.plugin
'context': request.context['neutron_context'],
'resource_type': request.context['resource'],
}
mock.patch(
'neutron.pecan_wsgi.controllers.root.CollectionsController.get',
@ -200,9 +201,6 @@ class TestRequestPopulatingHooks(PecanFunctionalTest):
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