From 3b12d76d6064798b15dda0bbd2b66acbec428159 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Wed, 30 Aug 2017 20:02:18 -0700 Subject: [PATCH] Pecan: Add missing emulated bulk create method This adds the simple bulk emulation logic that was present in the old legacy controller for plugins that do not support bulk operations. Closes-Bug: #1714355 Change-Id: I4ff02b9c5c007847edd18ec4ad6794257dd79576 (cherry picked from commit fb76c4f57d7d1cb6e74602d24a166c5d173917e0) --- neutron/pecan_wsgi/controllers/resource.py | 1 - neutron/pecan_wsgi/controllers/utils.py | 32 ++++++++++++++- .../functional/pecan_wsgi/test_controllers.py | 41 +++++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/neutron/pecan_wsgi/controllers/resource.py b/neutron/pecan_wsgi/controllers/resource.py index 87379272293..01900bd0d8b 100644 --- a/neutron/pecan_wsgi/controllers/resource.py +++ b/neutron/pecan_wsgi/controllers/resource.py @@ -58,7 +58,6 @@ class ItemController(utils.NeutronPecanController): def put(self, *args, **kwargs): neutron_context = request.context['neutron_context'] resources = request.context['resources'] - # TODO(kevinbenton): bulk? # Bulk update is not supported, 'resources' always contains a single # elemenet data = {self.resource: resources[0]} diff --git a/neutron/pecan_wsgi/controllers/utils.py b/neutron/pecan_wsgi/controllers/utils.py index 83dd9eaeabc..4d505babe55 100644 --- a/neutron/pecan_wsgi/controllers/utils.py +++ b/neutron/pecan_wsgi/controllers/utils.py @@ -18,6 +18,8 @@ import copy import functools from neutron_lib import constants +from oslo_log import log as logging +from oslo_utils import excutils import pecan from pecan import request @@ -28,6 +30,8 @@ from neutron import manager # Utility functions for Pecan controllers. +LOG = logging.getLogger(__name__) + class Fakecode(object): co_varnames = () @@ -227,8 +231,32 @@ class NeutronPecanController(object): @property def plugin_bulk_creator(self): - return getattr(self.plugin, - '%s_bulk' % self._plugin_handlers[self.CREATE]) + native = getattr(self.plugin, + '%s_bulk' % self._plugin_handlers[self.CREATE], + None) + # NOTE(kevinbenton): this flag is just to make testing easier since we + # don't have any in-tree plugins without native bulk support + if getattr(self.plugin, '_FORCE_EMULATED_BULK', False) or not native: + return self._emulated_bulk_creator + return native + + def _emulated_bulk_creator(self, context, **kwargs): + objs = [] + body = kwargs[self.collection] + try: + for item in body[self.collection]: + objs.append(self.plugin_creator(context, item)) + return objs + except Exception: + with excutils.save_and_reraise_exception(): + for obj in objs: + try: + self.plugin_deleter(context, obj['id']) + except Exception: + LOG.exception("Unable to undo bulk create for " + "%(resource)s %(id)s", + {'resource': self.collection, + 'id': obj['id']}) @property def plugin_deleter(self): diff --git a/neutron/tests/functional/pecan_wsgi/test_controllers.py b/neutron/tests/functional/pecan_wsgi/test_controllers.py index eb0c218de18..20e63522eeb 100644 --- a/neutron/tests/functional/pecan_wsgi/test_controllers.py +++ b/neutron/tests/functional/pecan_wsgi/test_controllers.py @@ -459,6 +459,47 @@ class TestResourceController(TestRootController): self.assertIn('ports', json_body) self.assertEqual(2, len(json_body['ports'])) + def test_emulated_bulk_create(self): + self.plugin._FORCE_EMULATED_BULK = True + response = self.app.post_json( + '/v2.0/ports.json', + params={'ports': [{'network_id': self.port['network_id'], + 'admin_state_up': True, + 'tenant_id': 'tenid'}, + {'network_id': self.port['network_id'], + 'admin_state_up': True, + 'tenant_id': 'tenid'}] + }, + headers={'X-Project-Id': 'tenid'}) + self.assertEqual(response.status_int, 201) + json_body = jsonutils.loads(response.body) + self.assertIn('ports', json_body) + self.assertEqual(2, len(json_body['ports'])) + + def test_emulated_bulk_create_rollback(self): + self.plugin._FORCE_EMULATED_BULK = True + response = self.app.post_json( + '/v2.0/ports.json', + params={'ports': [{'network_id': self.port['network_id'], + 'admin_state_up': True, + 'tenant_id': 'tenid'}, + {'network_id': self.port['network_id'], + 'admin_state_up': True, + 'tenant_id': 'tenid'}, + {'network_id': 'bad_net_id', + 'admin_state_up': True, + 'tenant_id': 'tenid'}] + }, + headers={'X-Project-Id': 'tenid'}, + expect_errors=True) + self.assertEqual(response.status_int, 400) + response = self.app.get( + '/v2.0/ports.json', + headers={'X-Project-Id': 'tenid'}) + # all ports should be rolled back from above so we are just left + # with the one created in setup + self.assertEqual(1, len(jsonutils.loads(response.body)['ports'])) + def test_bulk_create_one_item(self): response = self.app.post_json( '/v2.0/ports.json',