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
This commit is contained in:
Brandon Logan 2016-10-20 19:03:08 -05:00
parent 7fdb98cf17
commit 64252405c4
3 changed files with 47 additions and 1 deletions

View File

@ -152,7 +152,7 @@ class CollectionsController(utils.NeutronPecanController):
return self.create(resources) return self.create(resources)
def create(self, resources): def create(self, resources):
if len(resources) > 1: if request.context['is_bulk']:
# Bulk! # Bulk!
creator = self.plugin_bulk_creator creator = self.plugin_bulk_creator
key = self.collection key = self.collection

View File

@ -63,5 +63,7 @@ class BodyValidationHook(hooks.PecanHook):
if collection in data: if collection in data:
state.request.context['resources'] = [item[resource] for item in state.request.context['resources'] = [item[resource] for item in
data[collection]] data[collection]]
state.request.context['is_bulk'] = True
else: else:
state.request.context['resources'] = [data[resource]] state.request.context['resources'] = [data[resource]]
state.request.context['is_bulk'] = False

View File

@ -366,6 +366,35 @@ class TestResourceController(TestRootController):
self._test_method_returns_code('head', 405) self._test_method_returns_code('head', 405)
self._test_method_returns_code('delete', 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): class TestPaginationAndSorting(test_functional.PecanFunctionalTest):
@ -535,9 +564,11 @@ class TestRequestProcessing(TestRootController):
self.assertEqual('network', self.captured_context['resource']) self.assertEqual('network', self.captured_context['resource'])
self.assertEqual('networks', self.captured_context['collection']) self.assertEqual('networks', self.captured_context['collection'])
resources = self.captured_context['resources'] resources = self.captured_context['resources']
is_bulk = self.captured_context['is_bulk']
self.assertEqual(1, len(resources)) self.assertEqual(1, len(resources))
self.assertEqual('the_net', resources[0]['name']) self.assertEqual('the_net', resources[0]['name'])
self.assertTrue(resources[0]['admin_state_up']) self.assertTrue(resources[0]['admin_state_up'])
self.assertFalse(is_bulk)
def test_resource_processing_post_bulk(self): def test_resource_processing_post_bulk(self):
self.app.post_json( self.app.post_json(
@ -548,11 +579,24 @@ class TestRequestProcessing(TestRootController):
'admin_state_up': False}]}, 'admin_state_up': False}]},
headers={'X-Project-Id': 'tenid'}) headers={'X-Project-Id': 'tenid'})
resources = self.captured_context['resources'] resources = self.captured_context['resources']
is_bulk = self.captured_context['is_bulk']
self.assertEqual(2, len(resources)) self.assertEqual(2, len(resources))
self.assertTrue(resources[0]['admin_state_up']) self.assertTrue(resources[0]['admin_state_up'])
self.assertEqual('the_net_1', resources[0]['name']) self.assertEqual('the_net_1', resources[0]['name'])
self.assertFalse(resources[1]['admin_state_up']) self.assertFalse(resources[1]['admin_state_up'])
self.assertEqual('the_net_2', resources[1]['name']) 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): def test_resource_processing_post_unknown_attribute_returns_400(self):
response = self.app.post_json( response = self.app.post_json(