diff --git a/neutron/pecan_wsgi/controllers/resource.py b/neutron/pecan_wsgi/controllers/resource.py index 2848a88f0bc..91e6ef0d06b 100644 --- a/neutron/pecan_wsgi/controllers/resource.py +++ b/neutron/pecan_wsgi/controllers/resource.py @@ -60,7 +60,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 14995575a0a..0ee1101d524 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 @@ -30,6 +32,8 @@ from neutron_lib import exceptions # Utility functions for Pecan controllers. +LOG = logging.getLogger(__name__) + class Fakecode(object): co_varnames = () @@ -236,8 +240,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 019189fa56c..663cefd76b2 100644 --- a/neutron/tests/functional/pecan_wsgi/test_controllers.py +++ b/neutron/tests/functional/pecan_wsgi/test_controllers.py @@ -494,6 +494,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',