]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add attribute population hook
authorKevin Benton <blak111@gmail.com>
Thu, 11 Jun 2015 12:46:44 +0000 (05:46 -0700)
committerKevin Benton <kevinbenton@buttewifi.com>
Sat, 1 Aug 2015 18:55:56 +0000 (18:55 +0000)
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
neutron/newapi/hooks/__init__.py
neutron/newapi/hooks/attribute_population.py [new file with mode: 0644]
neutron/tests/functional/newapi/test_functional.py

index b8f7e6bb3a9e7444692d088cbd42a0d887de304c..4792868a02f9d405efbda936ad152de9e68d3183 100644 (file)
@@ -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(
index 69bf31cd91e1e60abed7ca24065586d4a4d0bdcb..1a7add439fa82dc5db8e72b02226bf850b8f6591 100644 (file)
@@ -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 (file)
index 0000000..938e40b
--- /dev/null
@@ -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 []
index 818f38c55bc7e95521cc0a176ae142512b868790..84620506a2cbed86b19c2e0d3f77999202d81106 100644 (file)
@@ -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):