neutron/neutron/pecan_wsgi/controllers/utils.py

268 lines
9.4 KiB
Python

# 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.
import copy
import functools
from neutron_lib import constants
import pecan
from pecan import request
import six
from neutron.api import api_common
from neutron.api.v2 import attributes as api_attributes
from neutron.db import api as db_api
from neutron import manager
# Utility functions for Pecan controllers.
class Fakecode(object):
co_varnames = ()
def _composed(*decorators):
"""Takes a list of decorators and returns a single decorator."""
def final_decorator(f):
for d in decorators:
# workaround for pecan bug that always assumes decorators
# have a __code__ attr
if not hasattr(d, '__code__'):
setattr(d, '__code__', Fakecode())
f = d(f)
return f
return final_decorator
def _protect_original_resources(f):
"""Wrapper to ensure that mutated resources are discarded on retries."""
@functools.wraps(f)
def wrapped(*args, **kwargs):
ctx = request.context
if 'resources' in ctx:
orig = ctx.get('protected_resources')
if not orig:
# this is the first call so we just take the whole reference
ctx['protected_resources'] = ctx['resources']
# TODO(blogan): Once bug 157751 is fixed and released in
# neutron-lib this memo will no longer be needed. This is just
# quick way to not depend on a release of neutron-lib.
# The version that has that bug fix will need to be updated in
# neutron-lib.
memo = {id(constants.ATTR_NOT_SPECIFIED):
constants.ATTR_NOT_SPECIFIED}
ctx['resources'] = copy.deepcopy(ctx['protected_resources'],
memo=memo)
return f(*args, **kwargs)
return wrapped
def _pecan_generator_wrapper(func, *args, **kwargs):
"""Helper function so we don't have to specify json for everything."""
kwargs.setdefault('content_type', 'application/json')
kwargs.setdefault('template', 'json')
return _composed(_protect_original_resources, db_api.retry_db_errors,
func(*args, **kwargs))
def expose(*args, **kwargs):
return _pecan_generator_wrapper(pecan.expose, *args, **kwargs)
def when(index, *args, **kwargs):
return _pecan_generator_wrapper(index.when, *args, **kwargs)
class NeutronPecanController(object):
LIST = 'list'
SHOW = 'show'
CREATE = 'create'
UPDATE = 'update'
DELETE = 'delete'
def __init__(self, collection, resource, plugin=None, resource_info=None,
allow_pagination=None, allow_sorting=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
# RESOURCE_ATTRIBUTE_MAP will not have anything in _resource_info
if self.resource_info:
self._mandatory_fields = set([field for (field, data) in
self.resource_info.items() if
data.get('required_by_policy')])
else:
self._mandatory_fields = set()
self.allow_pagination = allow_pagination
if self.allow_pagination is None:
self.allow_pagination = True
self.allow_sorting = allow_sorting
if self.allow_sorting is None:
self.allow_sorting = True
self.native_pagination = api_common.is_native_pagination_supported(
self.plugin)
self.native_sorting = api_common.is_native_sorting_supported(
self.plugin)
self.primary_key = self._get_primary_key()
self.parent = parent_resource
parent_resource = '_%s' % parent_resource if parent_resource else ''
self._parent_id_name = ('%s_id' % self.parent
if self.parent else None)
self._plugin_handlers = {
self.LIST: 'get%s_%s' % (parent_resource, self.collection),
self.SHOW: 'get%s_%s' % (parent_resource, self.resource)
}
for action in [self.CREATE, self.UPDATE, self.DELETE]:
self._plugin_handlers[action] = '%s%s_%s' % (
action, parent_resource, self.resource)
def build_field_list(self, request_fields):
added_fields = []
combined_fields = []
if request_fields:
req_fields_set = set(request_fields)
added_fields = self._mandatory_fields - req_fields_set
combined_fields = req_fields_set | self._mandatory_fields
return list(combined_fields), list(added_fields)
@property
def plugin(self):
if not self._plugin:
self._plugin = manager.NeutronManager.get_plugin_for_resource(
self.collection)
return self._plugin
@property
def resource_info(self):
if not self._resource_info:
self._resource_info = api_attributes.get_collection_info(
self.collection)
return self._resource_info
def _get_primary_key(self, default_primary_key='id'):
if not self.resource_info:
return default_primary_key
for key, value in six.iteritems(self.resource_info):
if value.get('primary_key', False):
return key
return default_primary_key
@property
def plugin_handlers(self):
return self._plugin_handlers
@property
def plugin_lister(self):
return getattr(self.plugin, self._plugin_handlers[self.LIST])
@property
def plugin_shower(self):
return getattr(self.plugin, self._plugin_handlers[self.SHOW])
@property
def plugin_creator(self):
return getattr(self.plugin, self._plugin_handlers[self.CREATE])
@property
def plugin_bulk_creator(self):
return getattr(self.plugin,
'%s_bulk' % self._plugin_handlers[self.CREATE])
@property
def plugin_deleter(self):
return getattr(self.plugin, self._plugin_handlers[self.DELETE])
@property
def plugin_updater(self):
return getattr(self.plugin, self._plugin_handlers[self.UPDATE])
class ShimRequest(object):
def __init__(self, context):
self.context = context
class ShimItemController(NeutronPecanController):
def __init__(self, collection, resource, item, controller):
super(ShimItemController, self).__init__(collection, resource)
self.item = item
self.controller_delete = getattr(controller, 'delete', None)
@expose(generic=True)
def index(self):
pecan.abort(405)
@when(index, method='DELETE')
def delete(self):
if not self.controller_delete:
pecan.abort(405)
pecan.response.status = 204
shim_request = ShimRequest(request.context['neutron_context'])
uri_identifiers = request.context['uri_identifiers']
return self.controller_delete(shim_request, self.item,
**uri_identifiers)
class ShimCollectionsController(NeutronPecanController):
def __init__(self, collection, resource, controller):
super(ShimCollectionsController, self).__init__(collection, resource)
self.controller = controller
self.controller_index = getattr(controller, 'index', None)
self.controller_create = getattr(controller, 'create', None)
@expose(generic=True)
def index(self):
if not self.controller_index:
pecan.abort(405)
shim_request = ShimRequest(request.context['neutron_context'])
uri_identifiers = request.context['uri_identifiers']
return self.controller_index(shim_request, **uri_identifiers)
@when(index, method='POST')
def create(self):
if not self.controller_create:
pecan.abort(405)
pecan.response.status = 201
shim_request = ShimRequest(request.context['neutron_context'])
uri_identifiers = request.context['uri_identifiers']
return self.controller_create(shim_request, request.json,
**uri_identifiers)
@expose()
def _lookup(self, item, *remainder):
request.context['resource'] = self.resource
request.context['resource_id'] = item
return ShimItemController(self.collection, self.resource, item,
self.controller), remainder
class PecanResourceExtension(object):
def __init__(self, collection, controller, plugin):
self.collection = collection
self.controller = controller
self.plugin = plugin