Merge "Add subresources support for PECAN"
This commit is contained in:
commit
374db46b9e
|
@ -26,9 +26,11 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ItemController(utils.NeutronPecanController):
|
class ItemController(utils.NeutronPecanController):
|
||||||
|
|
||||||
def __init__(self, resource, item, plugin=None, resource_info=None):
|
def __init__(self, resource, item, plugin=None, resource_info=None,
|
||||||
|
parent_resource=None):
|
||||||
super(ItemController, self).__init__(None, resource, plugin=plugin,
|
super(ItemController, self).__init__(None, resource, plugin=plugin,
|
||||||
resource_info=resource_info)
|
resource_info=resource_info,
|
||||||
|
parent_resource=parent_resource)
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
@utils.expose(generic=True)
|
@utils.expose(generic=True)
|
||||||
|
@ -37,9 +39,14 @@ class ItemController(utils.NeutronPecanController):
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
neutron_context = request.context['neutron_context']
|
neutron_context = request.context['neutron_context']
|
||||||
|
getter_args = [neutron_context, self.item]
|
||||||
|
# NOTE(tonytan4ever): This implicitly forces the getter method
|
||||||
|
# uses the parent_id as the last argument, thus easy for future
|
||||||
|
# refactoring
|
||||||
|
if 'parent_id' in request.context:
|
||||||
|
getter_args.append(request.context['parent_id'])
|
||||||
fields = request.context['query_params'].get('fields')
|
fields = request.context['query_params'].get('fields')
|
||||||
return {self.resource: self.plugin_shower(neutron_context, self.item,
|
return {self.resource: self.plugin_shower(*getter_args, fields=fields)}
|
||||||
fields=fields)}
|
|
||||||
|
|
||||||
@utils.when(index, method='HEAD')
|
@utils.when(index, method='HEAD')
|
||||||
@utils.when(index, method='POST')
|
@utils.when(index, method='POST')
|
||||||
|
@ -55,15 +62,21 @@ class ItemController(utils.NeutronPecanController):
|
||||||
# Bulk update is not supported, 'resources' always contains a single
|
# Bulk update is not supported, 'resources' always contains a single
|
||||||
# elemenet
|
# elemenet
|
||||||
data = {self.resource: resources[0]}
|
data = {self.resource: resources[0]}
|
||||||
return {self.resource: self.plugin_updater(neutron_context,
|
updater_args = [neutron_context, self.item]
|
||||||
self.item, data)}
|
if 'parent_id' in request.context:
|
||||||
|
updater_args.append(request.context['parent_id'])
|
||||||
|
updater_args.append(data)
|
||||||
|
return {self.resource: self.plugin_updater(*updater_args)}
|
||||||
|
|
||||||
@utils.when(index, method='DELETE')
|
@utils.when(index, method='DELETE')
|
||||||
def delete(self):
|
def delete(self):
|
||||||
# TODO(kevinbenton): setting code could be in a decorator
|
# TODO(kevinbenton): setting code could be in a decorator
|
||||||
pecan.response.status = 204
|
pecan.response.status = 204
|
||||||
neutron_context = request.context['neutron_context']
|
neutron_context = request.context['neutron_context']
|
||||||
return self.plugin_deleter(neutron_context, self.item)
|
deleter_args = [neutron_context, self.item]
|
||||||
|
if 'parent_id' in request.context:
|
||||||
|
deleter_args.append(request.context['parent_id'])
|
||||||
|
return self.plugin_deleter(*deleter_args)
|
||||||
|
|
||||||
@utils.expose()
|
@utils.expose()
|
||||||
def _lookup(self, collection, *remainder):
|
def _lookup(self, collection, *remainder):
|
||||||
|
@ -72,8 +85,10 @@ class ItemController(utils.NeutronPecanController):
|
||||||
collection)
|
collection)
|
||||||
if not controller:
|
if not controller:
|
||||||
LOG.warning(_LW("No controller found for: %s - returning response "
|
LOG.warning(_LW("No controller found for: %s - returning response "
|
||||||
"code 404"), collection)
|
"code 404"), collection)
|
||||||
pecan.abort(404)
|
pecan.abort(404)
|
||||||
|
request.context['resource'] = controller.resource
|
||||||
|
request.context['parent_id'] = request.context['resource_id']
|
||||||
return controller, remainder
|
return controller, remainder
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,7 +103,11 @@ class CollectionsController(utils.NeutronPecanController):
|
||||||
uri_identifier = '%s_id' % self.resource
|
uri_identifier = '%s_id' % self.resource
|
||||||
request.context['uri_identifiers'][uri_identifier] = item
|
request.context['uri_identifiers'][uri_identifier] = item
|
||||||
return (self.item_controller_class(
|
return (self.item_controller_class(
|
||||||
self.resource, item, resource_info=self.resource_info),
|
self.resource, item, resource_info=self.resource_info,
|
||||||
|
# NOTE(tonytan4ever): item needs to share the same
|
||||||
|
# parent as collection
|
||||||
|
parent_resource=self.parent
|
||||||
|
),
|
||||||
remainder)
|
remainder)
|
||||||
|
|
||||||
@utils.expose(generic=True)
|
@utils.expose(generic=True)
|
||||||
|
@ -96,10 +115,13 @@ class CollectionsController(utils.NeutronPecanController):
|
||||||
return self.get(*args, **kwargs)
|
return self.get(*args, **kwargs)
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
# NOTE(blogan): query_params is set in the QueryParametersHook
|
# NOTE(blogan): these are set in the FieldsAndFiltersHoook
|
||||||
query_params = request.context['query_params']
|
query_params = request.context['query_params']
|
||||||
neutron_context = request.context['neutron_context']
|
neutron_context = request.context['neutron_context']
|
||||||
return {self.collection: self.plugin_lister(neutron_context,
|
lister_args = [neutron_context]
|
||||||
|
if 'parent_id' in request.context:
|
||||||
|
lister_args.append(request.context['parent_id'])
|
||||||
|
return {self.collection: self.plugin_lister(*lister_args,
|
||||||
**query_params)}
|
**query_params)}
|
||||||
|
|
||||||
@utils.when(index, method='HEAD')
|
@utils.when(index, method='HEAD')
|
||||||
|
@ -127,4 +149,8 @@ class CollectionsController(utils.NeutronPecanController):
|
||||||
key = self.resource
|
key = self.resource
|
||||||
data = {key: resources[0]}
|
data = {key: resources[0]}
|
||||||
neutron_context = request.context['neutron_context']
|
neutron_context = request.context['neutron_context']
|
||||||
return {key: creator(neutron_context, data)}
|
creator_args = [neutron_context]
|
||||||
|
if 'parent_id' in request.context:
|
||||||
|
creator_args.append(request.context['parent_id'])
|
||||||
|
creator_args.append(data)
|
||||||
|
return {key: creator(*creator_args)}
|
||||||
|
|
|
@ -124,6 +124,7 @@ class NeutronPecanController(object):
|
||||||
self.plugin)
|
self.plugin)
|
||||||
self.primary_key = self._get_primary_key()
|
self.primary_key = self._get_primary_key()
|
||||||
|
|
||||||
|
self.parent = parent_resource
|
||||||
parent_resource = '_%s' % parent_resource if parent_resource else ''
|
parent_resource = '_%s' % parent_resource if parent_resource else ''
|
||||||
self._parent_id_name = ('%s_id' % parent_resource
|
self._parent_id_name = ('%s_id' % parent_resource
|
||||||
if parent_resource else None)
|
if parent_resource else None)
|
||||||
|
@ -166,6 +167,10 @@ class NeutronPecanController(object):
|
||||||
return key
|
return key
|
||||||
return default_primary_key
|
return default_primary_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin_handlers(self):
|
||||||
|
return self._plugin_handlers
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def plugin_lister(self):
|
def plugin_lister(self):
|
||||||
return getattr(self.plugin, self._plugin_handlers[self.LIST])
|
return getattr(self.plugin, self._plugin_handlers[self.LIST])
|
||||||
|
|
|
@ -35,7 +35,8 @@ def _custom_getter(resource, resource_id):
|
||||||
return quota.get_tenant_quotas(resource_id)[quotasv2.RESOURCE_NAME]
|
return quota.get_tenant_quotas(resource_id)[quotasv2.RESOURCE_NAME]
|
||||||
|
|
||||||
|
|
||||||
def fetch_resource(neutron_context, collection, resource, resource_id):
|
def fetch_resource(neutron_context, collection, resource, resource_id,
|
||||||
|
parent_id=None):
|
||||||
controller = manager.NeutronManager.get_controller_for_resource(
|
controller = manager.NeutronManager.get_controller_for_resource(
|
||||||
collection)
|
collection)
|
||||||
attrs = controller.resource_info
|
attrs = controller.resource_info
|
||||||
|
@ -50,9 +51,11 @@ def fetch_resource(neutron_context, collection, resource, resource_id):
|
||||||
value.get('primary_key') or 'default' not in value)]
|
value.get('primary_key') or 'default' not in value)]
|
||||||
plugin = manager.NeutronManager.get_plugin_for_resource(resource)
|
plugin = manager.NeutronManager.get_plugin_for_resource(resource)
|
||||||
if plugin:
|
if plugin:
|
||||||
getter = getattr(plugin, 'get_%s' % resource)
|
getter = controller.plugin_shower
|
||||||
# TODO(kevinbenton): the parent_id logic currently in base.py
|
getter_args = [neutron_context, resource_id]
|
||||||
return getter(neutron_context, resource_id, fields=field_list)
|
if parent_id:
|
||||||
|
getter_args.append(parent_id)
|
||||||
|
return getter(*getter_args, fields=field_list)
|
||||||
else:
|
else:
|
||||||
# Some legit resources, like quota, do not have a plugin yet.
|
# Some legit resources, like quota, do not have a plugin yet.
|
||||||
# Retrieving the original object is nevertheless important
|
# Retrieving the original object is nevertheless important
|
||||||
|
@ -81,8 +84,13 @@ class PolicyHook(hooks.PecanHook):
|
||||||
needs_prefetch = (state.request.method == 'PUT' or
|
needs_prefetch = (state.request.method == 'PUT' or
|
||||||
state.request.method == 'DELETE')
|
state.request.method == 'DELETE')
|
||||||
policy.init()
|
policy.init()
|
||||||
action = '%s_%s' % (pecan_constants.ACTION_MAP[state.request.method],
|
|
||||||
resource)
|
# 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]]
|
||||||
|
|
||||||
# NOTE(salv-orlando): As bulk updates are not supported, in case of PUT
|
# NOTE(salv-orlando): As bulk updates are not supported, in case of PUT
|
||||||
# requests there will be only a single item to process, and its
|
# requests there will be only a single item to process, and its
|
||||||
|
@ -97,8 +105,10 @@ class PolicyHook(hooks.PecanHook):
|
||||||
# Ops... this was a delete after all!
|
# Ops... this was a delete after all!
|
||||||
item = {}
|
item = {}
|
||||||
resource_id = state.request.context.get('resource_id')
|
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, collection,
|
||||||
resource, resource_id)
|
resource, resource_id,
|
||||||
|
parent_id=parent_id)
|
||||||
if resource_obj:
|
if resource_obj:
|
||||||
original_resources.append(resource_obj)
|
original_resources.append(resource_obj)
|
||||||
obj = copy.copy(resource_obj)
|
obj = copy.copy(resource_obj)
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from neutron_lib import constants as n_const
|
from neutron_lib import constants as n_const
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
@ -761,7 +763,8 @@ class TestShimControllers(test_functional.PecanFunctionalTest):
|
||||||
policy._ENFORCER.set_rules(
|
policy._ENFORCER.set_rules(
|
||||||
oslo_policy.Rules.from_dict(
|
oslo_policy.Rules.from_dict(
|
||||||
{'get_meh_meh': '',
|
{'get_meh_meh': '',
|
||||||
'get_meh_mehs': ''}),
|
'get_meh_mehs': '',
|
||||||
|
'get_fake_subresources': ''}),
|
||||||
overwrite=False)
|
overwrite=False)
|
||||||
self.addCleanup(policy.reset)
|
self.addCleanup(policy.reset)
|
||||||
|
|
||||||
|
@ -781,3 +784,19 @@ class TestShimControllers(test_functional.PecanFunctionalTest):
|
||||||
resp = self.app.get(url)
|
resp = self.app.get(url)
|
||||||
self.assertEqual(200, resp.status_int)
|
self.assertEqual(200, resp.status_int)
|
||||||
self.assertEqual({body_collection: [{'fake': 'fake'}]}, resp.json)
|
self.assertEqual({body_collection: [{'fake': 'fake'}]}, resp.json)
|
||||||
|
|
||||||
|
def test_hyphenated_collection_subresource_controller_not_shimmed(self):
|
||||||
|
body_collection = pecan_utils.FakeExtension.HYPHENATED_COLLECTION
|
||||||
|
uri_collection = body_collection.replace('_', '-')
|
||||||
|
# there is only one subresource so far
|
||||||
|
sub_resource_collection = (
|
||||||
|
pecan_utils.FakeExtension.FAKE_SUB_RESOURCE_COLLECTION)
|
||||||
|
temp_id = str(uuid.uuid1())
|
||||||
|
url = '/v2.0/{0}/{1}/{2}'.format(
|
||||||
|
uri_collection,
|
||||||
|
temp_id,
|
||||||
|
sub_resource_collection.replace('_', '-'))
|
||||||
|
resp = self.app.get(url)
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
self.assertEqual({sub_resource_collection: {'foo': temp_id}},
|
||||||
|
resp.json)
|
||||||
|
|
|
@ -110,6 +110,19 @@ class FakeExtension(extensions.ExtensionDescriptor):
|
||||||
HYPHENATED_RESOURCE = 'meh_meh'
|
HYPHENATED_RESOURCE = 'meh_meh'
|
||||||
HYPHENATED_COLLECTION = HYPHENATED_RESOURCE + 's'
|
HYPHENATED_COLLECTION = HYPHENATED_RESOURCE + 's'
|
||||||
|
|
||||||
|
SUB_RESOURCE_ATTRIBUTE_MAP = {
|
||||||
|
'fake_subresources': {
|
||||||
|
'parent': {
|
||||||
|
'collection_name': (
|
||||||
|
HYPHENATED_COLLECTION),
|
||||||
|
'member_name': HYPHENATED_RESOURCE},
|
||||||
|
'parameters': {'foo': {'is_visible': True},
|
||||||
|
'bar': {'is_visible': True}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FAKE_SUB_RESOURCE_COLLECTION = 'fake_subresources'
|
||||||
|
|
||||||
RAM = {
|
RAM = {
|
||||||
HYPHENATED_COLLECTION: {
|
HYPHENATED_COLLECTION: {
|
||||||
'fake': {'is_visible': True}
|
'fake': {'is_visible': True}
|
||||||
|
@ -137,12 +150,34 @@ class FakeExtension(extensions.ExtensionDescriptor):
|
||||||
params = self.RAM.get(self.HYPHENATED_COLLECTION, {})
|
params = self.RAM.get(self.HYPHENATED_COLLECTION, {})
|
||||||
attributes.PLURALS.update({self.HYPHENATED_COLLECTION:
|
attributes.PLURALS.update({self.HYPHENATED_COLLECTION:
|
||||||
self.HYPHENATED_RESOURCE})
|
self.HYPHENATED_RESOURCE})
|
||||||
|
fake_plugin = FakePlugin()
|
||||||
controller = base.create_resource(
|
controller = base.create_resource(
|
||||||
collection, self.HYPHENATED_RESOURCE, FakePlugin(),
|
collection, self.HYPHENATED_RESOURCE, FakePlugin(),
|
||||||
params, allow_bulk=True, allow_pagination=True,
|
params, allow_bulk=True, allow_pagination=True,
|
||||||
allow_sorting=True)
|
allow_sorting=True)
|
||||||
return [extensions.ResourceExtension(collection, controller,
|
resources = [extensions.ResourceExtension(collection,
|
||||||
attr_map=params)]
|
controller,
|
||||||
|
attr_map=params)]
|
||||||
|
for collection_name in self.SUB_RESOURCE_ATTRIBUTE_MAP:
|
||||||
|
resource_name = collection_name
|
||||||
|
parent = self.SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get(
|
||||||
|
'parent')
|
||||||
|
params = self.SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get(
|
||||||
|
'parameters')
|
||||||
|
|
||||||
|
controller = base.create_resource(collection_name, resource_name,
|
||||||
|
fake_plugin, params,
|
||||||
|
allow_bulk=True,
|
||||||
|
parent=parent)
|
||||||
|
|
||||||
|
resource = extensions.ResourceExtension(
|
||||||
|
collection_name,
|
||||||
|
controller, parent,
|
||||||
|
path_prefix="",
|
||||||
|
attr_map=params)
|
||||||
|
resources.append(resource)
|
||||||
|
|
||||||
|
return resources
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
def get_extended_resources(self, version):
|
||||||
if version == "2.0":
|
if version == "2.0":
|
||||||
|
@ -165,3 +200,7 @@ class FakePlugin(object):
|
||||||
|
|
||||||
def get_meh_mehs(self, context, filters=None, fields=None):
|
def get_meh_mehs(self, context, filters=None, fields=None):
|
||||||
return [{'fake': 'fake'}]
|
return [{'fake': 'fake'}]
|
||||||
|
|
||||||
|
def get_meh_meh_fake_subresources(self, context, id_, fields=None,
|
||||||
|
filters=None):
|
||||||
|
return {'foo': id_}
|
||||||
|
|
Loading…
Reference in New Issue