Add method for bulk creating objects.
There are APIs (like Neutron) which provide a way for creating multiple objects at once using single request. This change add possibility for performing such requests using Resource objects. Change-Id: I7d7c540ed1bada37ebebbe305dc0e36f38cff071
This commit is contained in:
@@ -1286,6 +1286,83 @@ class Resource(dict):
|
||||
return self.fetch(session)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def bulk_create(cls, session, data, prepend_key=True, base_path=None,
|
||||
**params):
|
||||
"""Create multiple remote resources based on this class and data.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:type session: :class:`~keystoneauth1.adapter.Adapter`
|
||||
:param data: list of dicts, which represent resources to create.
|
||||
:param prepend_key: A boolean indicating whether the resource_key
|
||||
should be prepended in a resource creation
|
||||
request. Default to True.
|
||||
:param str base_path: Base part of the URI for creating resources, if
|
||||
different from
|
||||
:data:`~openstack.resource.Resource.base_path`.
|
||||
:param dict params: Additional params to pass.
|
||||
|
||||
:return: A generator of :class:`Resource` objects.
|
||||
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
|
||||
:data:`Resource.allow_create` is not set to ``True``.
|
||||
"""
|
||||
if not cls.allow_create:
|
||||
raise exceptions.MethodNotSupported(cls, "create")
|
||||
|
||||
if not (data and isinstance(data, list)
|
||||
and all([isinstance(x, dict) for x in data])):
|
||||
raise ValueError('Invalid data passed: %s' % data)
|
||||
|
||||
session = cls._get_session(session)
|
||||
microversion = cls._get_microversion_for(cls, session, 'create')
|
||||
requires_id = (cls.create_requires_id
|
||||
if cls.create_requires_id is not None
|
||||
else cls.create_method == 'PUT')
|
||||
if cls.create_method == 'PUT':
|
||||
method = session.put
|
||||
elif cls.create_method == 'POST':
|
||||
method = session.post
|
||||
else:
|
||||
raise exceptions.ResourceFailure(
|
||||
msg="Invalid create method: %s" % cls.create_method)
|
||||
|
||||
body = []
|
||||
resources = []
|
||||
for attrs in data:
|
||||
# NOTE(gryf): we need to create resource objects, since
|
||||
# _prepare_request only works on instances, not classes.
|
||||
# Those objects will be used in case where request doesn't return
|
||||
# JSON data representing created resource, and yet it's required
|
||||
# to return newly created resource objects.
|
||||
resource = cls.new(connection=session._get_connection(), **attrs)
|
||||
resources.append(resource)
|
||||
request = resource._prepare_request(requires_id=requires_id,
|
||||
base_path=base_path)
|
||||
body.append(request.body)
|
||||
|
||||
if prepend_key:
|
||||
body = {cls.resources_key: body}
|
||||
|
||||
response = method(request.url, json=body, headers=request.headers,
|
||||
microversion=microversion, params=params)
|
||||
exceptions.raise_from_response(response)
|
||||
data = response.json()
|
||||
|
||||
if cls.resources_key:
|
||||
data = data[cls.resources_key]
|
||||
|
||||
if not isinstance(data, list):
|
||||
data = [data]
|
||||
|
||||
has_body = (cls.has_body if cls.create_returns_body is None
|
||||
else cls.create_returns_body)
|
||||
if has_body and cls.create_returns_body is False:
|
||||
return (r.fetch(session) for r in resources)
|
||||
else:
|
||||
return (cls.existing(microversion=microversion,
|
||||
connection=session._get_connection(),
|
||||
**res_dict) for res_dict in data)
|
||||
|
||||
def fetch(self, session, requires_id=True,
|
||||
base_path=None, error_message=None, **params):
|
||||
"""Get a remote resource based on this instance.
|
||||
|
@@ -2487,6 +2487,136 @@ class TestResourceActions(base.TestCase):
|
||||
# Ensure we only made two calls to get this done
|
||||
self.assertEqual(2, len(self.session.get.call_args_list))
|
||||
|
||||
def test_bulk_create_invalid_data_passed(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'POST'
|
||||
allow_create = True
|
||||
|
||||
Test._prepare_request = mock.Mock()
|
||||
self.assertRaises(ValueError, Test.bulk_create, self.session, [])
|
||||
self.assertRaises(ValueError, Test.bulk_create, self.session, None)
|
||||
self.assertRaises(ValueError, Test.bulk_create, self.session, object)
|
||||
self.assertRaises(ValueError, Test.bulk_create, self.session, {})
|
||||
self.assertRaises(ValueError, Test.bulk_create, self.session, "hi!")
|
||||
self.assertRaises(ValueError, Test.bulk_create, self.session, ["hi!"])
|
||||
|
||||
def _test_bulk_create(self, cls, http_method, microversion=None,
|
||||
base_path=None, **params):
|
||||
req1 = mock.Mock()
|
||||
req2 = mock.Mock()
|
||||
req1.body = {'name': 'resource1'}
|
||||
req2.body = {'name': 'resource2'}
|
||||
req1.url = 'uri'
|
||||
req2.url = 'uri'
|
||||
req1.headers = 'headers'
|
||||
req2.headers = 'headers'
|
||||
|
||||
request_body = {"tests": [{'name': 'resource1', 'id': 'id1'},
|
||||
{'name': 'resource2', 'id': 'id2'}]}
|
||||
|
||||
cls._prepare_request = mock.Mock(side_effect=[req1, req2])
|
||||
mock_response = mock.Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.links = {}
|
||||
mock_response.json.return_value = request_body
|
||||
http_method.return_value = mock_response
|
||||
|
||||
res = list(cls.bulk_create(self.session, [{'name': 'resource1'},
|
||||
{'name': 'resource2'}],
|
||||
base_path=base_path, **params))
|
||||
|
||||
self.assertEqual(len(res), 2)
|
||||
self.assertEqual(res[0].id, 'id1')
|
||||
self.assertEqual(res[1].id, 'id2')
|
||||
http_method.assert_called_once_with(self.request.url,
|
||||
json={'tests': [req1.body,
|
||||
req2.body]},
|
||||
headers=self.request.headers,
|
||||
microversion=microversion,
|
||||
params=params)
|
||||
|
||||
def test_bulk_create_post(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'POST'
|
||||
allow_create = True
|
||||
resources_key = 'tests'
|
||||
|
||||
self._test_bulk_create(Test, self.session.post)
|
||||
|
||||
def test_bulk_create_put(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'PUT'
|
||||
allow_create = True
|
||||
resources_key = 'tests'
|
||||
|
||||
self._test_bulk_create(Test, self.session.put)
|
||||
|
||||
def test_bulk_create_with_params(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'POST'
|
||||
allow_create = True
|
||||
resources_key = 'tests'
|
||||
|
||||
self._test_bulk_create(Test, self.session.post, answer=42)
|
||||
|
||||
def test_bulk_create_with_microversion(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'POST'
|
||||
allow_create = True
|
||||
resources_key = 'tests'
|
||||
_max_microversion = '1.42'
|
||||
|
||||
self._test_bulk_create(Test, self.session.post, microversion='1.42')
|
||||
|
||||
def test_bulk_create_with_base_path(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'POST'
|
||||
allow_create = True
|
||||
resources_key = 'tests'
|
||||
|
||||
self._test_bulk_create(Test, self.session.post, base_path='dummy')
|
||||
|
||||
def test_bulk_create_fail(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'POST'
|
||||
allow_create = False
|
||||
resources_key = 'tests'
|
||||
|
||||
self.assertRaises(exceptions.MethodNotSupported, Test.bulk_create,
|
||||
self.session, [{'name': 'name'}])
|
||||
|
||||
def test_bulk_create_fail_on_request(self):
|
||||
class Test(resource.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
create_method = 'POST'
|
||||
allow_create = True
|
||||
resources_key = 'tests'
|
||||
|
||||
response = FakeResponse({}, status_code=409)
|
||||
response.content = ('{"TestError": {"message": "Failed to parse '
|
||||
'request. Required attribute \'foo\' not '
|
||||
'specified", "type": "HTTPBadRequest", '
|
||||
'"detail": ""}}')
|
||||
response.reason = 'Bad Request'
|
||||
self.session.post.return_value = response
|
||||
self.assertRaises(exceptions.ConflictException, Test.bulk_create,
|
||||
self.session, [{'name': 'name'}])
|
||||
|
||||
|
||||
class TestResourceFind(base.TestCase):
|
||||
|
||||
|
Reference in New Issue
Block a user