From 64252405c4780f09b376655b1b439543290a2f65 Mon Sep 17 00:00:00 2001 From: Brandon Logan Date: Thu, 20 Oct 2016 19:03:08 -0500 Subject: [PATCH] Pecan: Bulk create with one item returns plural If a bulk create happened with a single item, the pecan code would return back to the user the singular version of the resource in the body. For example, to bulk create many security group rules, the user would give in the body of the request the json with a parent key of "security-group-rules" with a value of a list of json security group rules. If this list is of length one, then after the creation of the one security group rule, the API would return back to the client "security-group-rule" as the parent key. This is not how the legacy wsgi layer behaved. The behavior is expected when nova creates security group rules by calling the neutron API through its deprecated security groups API. Change-Id: I8757630403e4d486cd3c8dd6f041e9ee326ba3b4 Closes-Bug: #1633671 --- neutron/pecan_wsgi/controllers/resource.py | 2 +- neutron/pecan_wsgi/hooks/body_validation.py | 2 + .../functional/pecan_wsgi/test_controllers.py | 44 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/neutron/pecan_wsgi/controllers/resource.py b/neutron/pecan_wsgi/controllers/resource.py index 6909fb65950..c3e3bb18eb3 100644 --- a/neutron/pecan_wsgi/controllers/resource.py +++ b/neutron/pecan_wsgi/controllers/resource.py @@ -152,7 +152,7 @@ class CollectionsController(utils.NeutronPecanController): return self.create(resources) def create(self, resources): - if len(resources) > 1: + if request.context['is_bulk']: # Bulk! creator = self.plugin_bulk_creator key = self.collection diff --git a/neutron/pecan_wsgi/hooks/body_validation.py b/neutron/pecan_wsgi/hooks/body_validation.py index 1b50abb63e4..da156e74809 100644 --- a/neutron/pecan_wsgi/hooks/body_validation.py +++ b/neutron/pecan_wsgi/hooks/body_validation.py @@ -63,5 +63,7 @@ class BodyValidationHook(hooks.PecanHook): if collection in data: state.request.context['resources'] = [item[resource] for item in data[collection]] + state.request.context['is_bulk'] = True else: state.request.context['resources'] = [data[resource]] + state.request.context['is_bulk'] = False diff --git a/neutron/tests/functional/pecan_wsgi/test_controllers.py b/neutron/tests/functional/pecan_wsgi/test_controllers.py index 9b2e02736f3..f10a2826355 100644 --- a/neutron/tests/functional/pecan_wsgi/test_controllers.py +++ b/neutron/tests/functional/pecan_wsgi/test_controllers.py @@ -366,6 +366,35 @@ class TestResourceController(TestRootController): self._test_method_returns_code('head', 405) self._test_method_returns_code('delete', 405) + def test_bulk_create(self): + 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_bulk_create_one_item(self): + response = self.app.post_json( + '/v2.0/ports.json', + params={'ports': [{'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(1, len(json_body['ports'])) + class TestPaginationAndSorting(test_functional.PecanFunctionalTest): @@ -535,9 +564,11 @@ class TestRequestProcessing(TestRootController): self.assertEqual('network', self.captured_context['resource']) self.assertEqual('networks', self.captured_context['collection']) resources = self.captured_context['resources'] + is_bulk = self.captured_context['is_bulk'] self.assertEqual(1, len(resources)) self.assertEqual('the_net', resources[0]['name']) self.assertTrue(resources[0]['admin_state_up']) + self.assertFalse(is_bulk) def test_resource_processing_post_bulk(self): self.app.post_json( @@ -548,11 +579,24 @@ class TestRequestProcessing(TestRootController): 'admin_state_up': False}]}, headers={'X-Project-Id': 'tenid'}) resources = self.captured_context['resources'] + is_bulk = self.captured_context['is_bulk'] self.assertEqual(2, len(resources)) self.assertTrue(resources[0]['admin_state_up']) self.assertEqual('the_net_1', resources[0]['name']) self.assertFalse(resources[1]['admin_state_up']) self.assertEqual('the_net_2', resources[1]['name']) + self.assertTrue(is_bulk) + + def test_resource_processing_post_bulk_one_item(self): + self.app.post_json( + '/v2.0/networks.json', + params={'networks': [{'name': 'the_net_1', + 'admin_state_up': True}]}, + headers={'X-Project-Id': 'tenid'}) + resources = self.captured_context['resources'] + is_bulk = self.captured_context['is_bulk'] + self.assertEqual(1, len(resources)) + self.assertTrue(is_bulk) def test_resource_processing_post_unknown_attribute_returns_400(self): response = self.app.post_json(