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
This commit is contained in:
parent
9e87a70a1a
commit
6e908dd417
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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}
|
||||
|
30
neutron/pecan_wsgi/hooks/utils.py
Normal file
30
neutron/pecan_wsgi/hooks/utils.py
Normal file
@ -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)
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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_}
|
||||
|
Loading…
Reference in New Issue
Block a user