From 7cc7c2deb8a3116d44a54da950837cadcf065bd7 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Thu, 11 Jun 2015 05:46:44 -0700 Subject: [PATCH] Add attribute population hook Add a hook to populate POST and PUT request bodies with all of the necessary attributes that the caller didn't specifiy. Partially-Implements: blueprint wsgi-pecan-switch Change-Id: I4afe09ea6cb1bcdf887ae4e36a473f713fe006cf --- neutron/newapi/app.py | 1 + neutron/newapi/hooks/__init__.py | 2 + neutron/newapi/hooks/attribute_population.py | 89 +++++++++++++++++++ .../functional/newapi/test_functional.py | 29 +++++- 4 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 neutron/newapi/hooks/attribute_population.py diff --git a/neutron/newapi/app.py b/neutron/newapi/app.py index b8f7e6bb3..4792868a0 100644 --- a/neutron/newapi/app.py +++ b/neutron/newapi/app.py @@ -45,6 +45,7 @@ def setup_app(*args, **kwargs): hooks.ExceptionTranslationHook(), # priority 100 hooks.ContextHook(), # priority 95 hooks.ResourceIdentifierHook(), # priority 95 + hooks.AttributePopulationHook(), # priority 120 (depends on 2 above) ] app = pecan.make_app( diff --git a/neutron/newapi/hooks/__init__.py b/neutron/newapi/hooks/__init__.py index 69bf31cd9..1a7add439 100644 --- a/neutron/newapi/hooks/__init__.py +++ b/neutron/newapi/hooks/__init__.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron.newapi.hooks import attribute_population from neutron.newapi.hooks import context from neutron.newapi.hooks import resource_identifier from neutron.newapi.hooks import translation @@ -21,3 +22,4 @@ from neutron.newapi.hooks import translation ExceptionTranslationHook = translation.ExceptionTranslationHook ContextHook = context.ContextHook ResourceIdentifierHook = resource_identifier.ResourceIdentifierHook +AttributePopulationHook = attribute_population.AttributePopulationHook diff --git a/neutron/newapi/hooks/attribute_population.py b/neutron/newapi/hooks/attribute_population.py new file mode 100644 index 000000000..938e40b4a --- /dev/null +++ b/neutron/newapi/hooks/attribute_population.py @@ -0,0 +1,89 @@ +# 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 hooks + +from neutron.api.v2 import attributes +from neutron.api.v2 import base as v2base + + +class AttributePopulationHook(hooks.PecanHook): + + priority = 120 + + def before(self, state): + state.request.prepared_data = {} + state.request.resources = [] + if state.request.method not in ('POST', 'PUT'): + return + is_create = state.request.method == 'POST' + resource = state.request.resource_type + if not resource: + return + state.request.prepared_data = v2base.Controller.prepare_request_body( + state.request.context, state.request.json, is_create, resource, + _attributes_for_resource(resource)) + state.request.resources = _extract_resources_from_state(state) + # make the original object available: + if not is_create: + obj_id = _pull_id_from_request(state.request) + 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 + 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) + state.request.original_object = obj + + +def _attributes_for_resource(resource): + if resource not in attributes.PLURALS.values(): + return {} + return attributes.RESOURCE_ATTRIBUTE_MAP.get( + _plural(resource), {}) + + +def _pull_id_from_request(request): + # 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) + jsontrail = request.path_info.replace('/v2.0/%s/' % resources, '') + obj_id = jsontrail.replace('.json', '') + return obj_id + + +def _plural(rtype): + for plural, single in attributes.PLURALS.items(): + if rtype == single: + return plural + + +def _extract_resources_from_state(state): + resource_type = state.request.resource_type + if not resource_type: + return [] + data = state.request.prepared_data + # single item + if resource_type in data: + return [data[resource_type]] + # multiple items + if _plural(resource_type) in data: + return data[_plural(resource_type)] + + return [] diff --git a/neutron/tests/functional/newapi/test_functional.py b/neutron/tests/functional/newapi/test_functional.py index 818f38c55..84620506a 100644 --- a/neutron/tests/functional/newapi/test_functional.py +++ b/neutron/tests/functional/newapi/test_functional.py @@ -23,7 +23,9 @@ from pecan import set_config from pecan.testing import load_test_app import testtools +from neutron.api.v2 import attributes from neutron.common import exceptions as n_exc +from neutron import context from neutron import manager from neutron.tests.unit import testlib_api @@ -42,6 +44,21 @@ class PecanFunctionalTest(testlib_api.SqlTestCase): os.path.dirname(__file__), 'config.py' )) + self._gen_port() + + def _gen_port(self): + pl = manager.NeutronManager.get_plugin() + network_id = pl.create_network(context.get_admin_context(), { + 'network': + {'name': 'pecannet', 'tenant_id': 'tenid', 'shared': False, + 'admin_state_up': True, 'status': 'ACTIVE'}})['id'] + self.port = pl.create_port(context.get_admin_context(), { + 'port': + {'tenant_id': 'tenid', 'network_id': network_id, + 'fixed_ips': attributes.ATTR_NOT_SPECIFIED, + 'mac_address': '00:11:22:33:44:55', + 'admin_state_up': True, 'device_id': 'FF', + 'device_owner': 'pecan', 'name': 'pecan'}}) def set_config_overrides(self): cfg.CONF.set_override('auth_strategy', 'noauth') @@ -55,16 +72,20 @@ class TestV2Controller(PecanFunctionalTest): def test_post(self): response = self.app.post_json('/v2.0/ports.json', - params={'port': {'name': 'test'}}) + params={'port': {'network_id': self.port['network_id'], + 'admin_state_up': True, + 'tenant_id': 'tenid'}}, + headers={'X-Tenant-Id': 'tenid'}) self.assertEqual(response.status_int, 200) def test_put(self): - response = self.app.put_json('/v2.0/ports/44.json', - params={'port': {'name': 'test'}}) + response = self.app.put_json('/v2.0/ports/%s.json' % self.port['id'], + params={'port': {'name': 'test'}}, + headers={'X-Tenant-Id': 'tenid'}) self.assertEqual(response.status_int, 200) def test_delete(self): - response = self.app.delete('/v2.0/ports/44.json') + response = self.app.delete('/v2.0/ports/%s.json' % self.port['id']) self.assertEqual(response.status_int, 200) def test_plugin_initialized(self): -- 2.45.2