diff --git a/openstack/message/message_service.py b/openstack/message/message_service.py index 32f827c11..e74bf80bb 100644 --- a/openstack/message/message_service.py +++ b/openstack/message/message_service.py @@ -16,7 +16,8 @@ from openstack import service_filter class MessageService(service_filter.ServiceFilter): """The message service.""" - valid_versions = [service_filter.ValidVersion('v1')] + valid_versions = [service_filter.ValidVersion('v1'), + service_filter.ValidVersion('v2')] def __init__(self, version=None): """Create a message service.""" diff --git a/openstack/message/v2/__init__.py b/openstack/message/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/message/v2/_proxy.py b/openstack/message/v2/_proxy.py new file mode 100644 index 000000000..c3b5079b3 --- /dev/null +++ b/openstack/message/v2/_proxy.py @@ -0,0 +1,73 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstack.message.v2 import queue as _queue +from openstack import proxy2 + + +class Proxy(proxy2.BaseProxy): + + def create_queue(self, **attrs): + """Create a new queue from attributes + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~openstack.message.v2.queue.Queue`, + comprised of the properties on the Queue class. + + :returns: The results of queue creation + :rtype: :class:`~openstack.message.v2.queue.Queue` + """ + return self._create(_queue.Queue, **attrs) + + def get_queue(self, queue): + """Get a queue + + :param queue: The value can be the name of a queue or a + :class:`~openstack.message.v2.queue.Queue` instance. + + :returns: One :class:`~openstack.message.v2.queue.Queue` + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + queue matching the name could be found. + """ + return self._get(_queue.Queue, queue) + + def queues(self, **query): + """Retrieve a generator of queues + + :param kwargs \*\*query: Optional query parameters to be sent to + restrict the queues to be returned. Available parameters include: + + * limit: Requests at most the specified number of items be + returned from the query. + * marker: Specifies the ID of the last-seen queue. Use the limit + parameter to make an initial limited request and use the ID of + the last-seen queue from the response as the marker parameter + value in a subsequent limited request. + + :returns: A generator of queue instances. + """ + return self._list(_queue.Queue, paginated=True, **query) + + def delete_queue(self, value, ignore_missing=True): + """Delete a queue + + :param value: The value can be either the name of a queue or a + :class:`~openstack.message.v2.queue.Queue` instance. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the queue does not exist. + When set to ``True``, no exception will be set when + attempting to delete a nonexistent queue. + + :returns: ``None`` + """ + return self._delete(_queue.Queue, value, ignore_missing=ignore_missing) diff --git a/openstack/message/v2/queue.py b/openstack/message/v2/queue.py new file mode 100644 index 000000000..0d3a3ed99 --- /dev/null +++ b/openstack/message/v2/queue.py @@ -0,0 +1,128 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from openstack.message import message_service +from openstack import resource2 + + +class Queue(resource2.Resource): + resources_key = "queues" + base_path = "/queues" + service = message_service.MessageService() + + # capabilities + allow_create = True + allow_list = True + allow_get = True + allow_delete = True + + # Properties + #: The default TTL of messages defined for a queue, which will effect for + #: any messages posted to the queue. + default_message_ttl = resource2.Body("_default_message_ttl") + #: Description of the queue. + description = resource2.Body("description") + #: The max post size of messages defined for a queue, which will effect + #: for any messages posted to the queue. + max_messages_post_size = resource2.Body("_max_messages_post_size") + #: Name of the queue. The name is the unique identity of a queue. It + #: must not exceed 64 bytes in length, and it is limited to US-ASCII + #: letters, digits, underscores, and hyphens. + name = resource2.Body("name", alternate_id=True) + #: The ID to identify the client accessing Zaqar API. Must be specified + #: in header for each API request. + client_id = resource2.Header("Client-ID") + #: The ID to identify the project accessing Zaqar API. Must be specified + #: in case keystone auth is not enabled in Zaqar service. + project_id = resource2.Header("X-PROJECT-ID") + + def create(self, session): + request = self._prepare_request(requires_id=True, prepend_key=True) + headers = { + "Client-ID": self.client_id or str(uuid.uuid4()), + "X-PROJECT-ID": self.project_id or session.get_project_id() + } + request.headers.update(headers) + response = session.put(request.uri, endpoint_filter=self.service, + json=request.body, headers=request.headers) + + self._translate_response(response, has_body=False) + return self + + @classmethod + def list(cls, session, paginated=False, **params): + """This method is a generator which yields queue objects. + + This is almost the copy of list method of resource2.Resource class. + The only difference is the request header now includes `Client-ID` + and `X-PROJECT-ID` fields which are required by Zaqar v2 API. + """ + more_data = True + query_params = cls._query_mapping._transpose(params) + uri = cls.base_path % params + headers = { + "Client-ID": params.get('client_id', None) or str(uuid.uuid4()), + "X-PROJECT-ID": params.get('project_id', None + ) or session.get_project_id() + } + + while more_data: + resp = session.get(uri, endpoint_filter=cls.service, + headers=headers, params=query_params) + resp = resp.json() + resp = resp[cls.resources_key] + + if not resp: + more_data = False + + yielded = 0 + new_marker = None + for data in resp: + value = cls.existing(**data) + new_marker = value.id + yielded += 1 + yield value + + if not paginated: + return + if "limit" in query_params and yielded < query_params["limit"]: + return + query_params["limit"] = yielded + query_params["marker"] = new_marker + + def get(self, session): + request = self._prepare_request() + headers = { + "Client-ID": self.client_id or str(uuid.uuid4()), + "X-PROJECT-ID": self.project_id or session.get_project_id() + } + request.headers.update(headers) + response = session.get(request.uri, endpoint_filter=self.service, + headers=headers) + self._translate_response(response) + + return self + + def delete(self, session): + request = self._prepare_request() + headers = { + "Client-ID": self.client_id or str(uuid.uuid4()), + "X-PROJECT-ID": self.project_id or session.get_project_id() + } + request.headers.update(headers) + response = session.delete(request.uri, endpoint_filter=self.service, + headers=headers) + + self._translate_response(response, has_body=False) + return self diff --git a/openstack/tests/unit/message/test_message_service.py b/openstack/tests/unit/message/test_message_service.py index 7ba6626d2..66d074fc1 100644 --- a/openstack/tests/unit/message/test_message_service.py +++ b/openstack/tests/unit/message/test_message_service.py @@ -23,6 +23,8 @@ class TestMessageService(testtools.TestCase): self.assertEqual('public', sot.interface) self.assertIsNone(sot.region) self.assertIsNone(sot.service_name) - self.assertEqual(1, len(sot.valid_versions)) + self.assertEqual(2, len(sot.valid_versions)) self.assertEqual('v1', sot.valid_versions[0].module) self.assertEqual('v1', sot.valid_versions[0].path) + self.assertEqual('v2', sot.valid_versions[1].module) + self.assertEqual('v2', sot.valid_versions[1].path) diff --git a/openstack/tests/unit/message/v2/__init__.py b/openstack/tests/unit/message/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack/tests/unit/message/v2/test_proxy.py b/openstack/tests/unit/message/v2/test_proxy.py new file mode 100644 index 000000000..ba36495d0 --- /dev/null +++ b/openstack/tests/unit/message/v2/test_proxy.py @@ -0,0 +1,38 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstack.message.v2 import _proxy +from openstack.message.v2 import queue +from openstack.tests.unit import test_proxy_base2 + +QUEUE_NAME = 'test_queue' + + +class TestMessageProxy(test_proxy_base2.TestProxyBase): + def setUp(self): + super(TestMessageProxy, self).setUp() + self.proxy = _proxy.Proxy(self.session) + + def test_queue_create(self): + self.verify_create(self.proxy.create_queue, queue.Queue) + + def test_queue_get(self): + self.verify_get(self.proxy.get_queue, queue.Queue) + + def test_queues(self): + self.verify_list(self.proxy.queues, queue.Queue, paginated=True) + + def test_queue_delete(self): + self.verify_delete(self.proxy.delete_queue, queue.Queue, False) + + def test_queue_delete_ignore(self): + self.verify_delete(self.proxy.delete_queue, queue.Queue, True) diff --git a/openstack/tests/unit/message/v2/test_queue.py b/openstack/tests/unit/message/v2/test_queue.py new file mode 100644 index 000000000..566510c67 --- /dev/null +++ b/openstack/tests/unit/message/v2/test_queue.py @@ -0,0 +1,171 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import testtools +import uuid + +from openstack.message.v2 import queue + + +FAKE1 = { + 'name': 'test_queue', + 'description': 'Queue used for test.', + '_default_message_ttl': 3600, + '_max_messages_post_size': 262144 +} + + +FAKE2 = { + 'name': 'test_queue', + 'description': 'Queue used for test.', + '_default_message_ttl': 3600, + '_max_messages_post_size': 262144, + 'client_id': 'OLD_CLIENT_ID', + 'project_id': 'OLD_PROJECT_ID' +} + + +class TestQueue(testtools.TestCase): + def test_basic(self): + sot = queue.Queue() + self.assertEqual('queues', sot.resources_key) + self.assertEqual('/queues', sot.base_path) + self.assertEqual('messaging', sot.service.service_type) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_get) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = queue.Queue.new(**FAKE2) + self.assertEqual(FAKE1['description'], sot.description) + self.assertEqual(FAKE1['name'], sot.name) + self.assertEqual(FAKE1['name'], sot.id) + self.assertEqual(FAKE1['_default_message_ttl'], + sot.default_message_ttl) + self.assertEqual(FAKE1['_max_messages_post_size'], + sot.max_messages_post_size) + self.assertEqual(FAKE2['client_id'], sot.client_id) + self.assertEqual(FAKE2['project_id'], sot.project_id) + + @mock.patch.object(uuid, 'uuid4') + def test_create(self, mock_uuid): + sess = mock.Mock() + resp = mock.Mock() + sess.put.return_value = resp + sess.get_project_id.return_value = 'NEW_PROJECT_ID' + mock_uuid.return_value = 'NEW_CLIENT_ID' + + sot = queue.Queue(**FAKE1) + sot._translate_response = mock.Mock() + res = sot.create(sess) + + url = 'queues/%s' % FAKE1['name'] + headers = {'Client-ID': 'NEW_CLIENT_ID', + 'X-PROJECT-ID': 'NEW_PROJECT_ID'} + sess.put.assert_called_with(url, endpoint_filter=sot.service, + headers=headers, json=FAKE1) + sess.get_project_id.assert_called_once_with() + sot._translate_response.assert_called_once_with(resp, has_body=False) + self.assertEqual(sot, res) + + def test_create_client_id_project_id_exist(self): + sess = mock.Mock() + resp = mock.Mock() + sess.put.return_value = resp + + sot = queue.Queue(**FAKE2) + sot._translate_response = mock.Mock() + res = sot.create(sess) + + url = 'queues/%s' % FAKE2['name'] + headers = {'Client-ID': 'OLD_CLIENT_ID', + 'X-PROJECT-ID': 'OLD_PROJECT_ID'} + sess.put.assert_called_with(url, endpoint_filter=sot.service, + headers=headers, json=FAKE1) + sot._translate_response.assert_called_once_with(resp, has_body=False) + self.assertEqual(sot, res) + + @mock.patch.object(uuid, 'uuid4') + def test_get(self, mock_uuid): + sess = mock.Mock() + resp = mock.Mock() + sess.get.return_value = resp + sess.get_project_id.return_value = 'NEW_PROJECT_ID' + mock_uuid.return_value = 'NEW_CLIENT_ID' + + sot = queue.Queue(**FAKE1) + sot._translate_response = mock.Mock() + res = sot.get(sess) + + url = 'queues/%s' % FAKE1['name'] + headers = {'Client-ID': 'NEW_CLIENT_ID', + 'X-PROJECT-ID': 'NEW_PROJECT_ID'} + sess.get.assert_called_with(url, endpoint_filter=sot.service, + headers=headers) + sess.get_project_id.assert_called_once_with() + sot._translate_response.assert_called_once_with(resp) + self.assertEqual(sot, res) + + def test_get_client_id_project_id_exist(self): + sess = mock.Mock() + resp = mock.Mock() + sess.get.return_value = resp + + sot = queue.Queue(**FAKE2) + sot._translate_response = mock.Mock() + res = sot.get(sess) + + url = 'queues/%s' % FAKE2['name'] + headers = {'Client-ID': 'OLD_CLIENT_ID', + 'X-PROJECT-ID': 'OLD_PROJECT_ID'} + sess.get.assert_called_with(url, endpoint_filter=sot.service, + headers=headers) + sot._translate_response.assert_called_once_with(resp) + self.assertEqual(sot, res) + + @mock.patch.object(uuid, 'uuid4') + def test_delete(self, mock_uuid): + sess = mock.Mock() + resp = mock.Mock() + sess.delete.return_value = resp + sess.get_project_id.return_value = 'NEW_PROJECT_ID' + mock_uuid.return_value = 'NEW_CLIENT_ID' + + sot = queue.Queue(**FAKE1) + sot._translate_response = mock.Mock() + sot.delete(sess) + + url = 'queues/%s' % FAKE1['name'] + headers = {'Client-ID': 'NEW_CLIENT_ID', + 'X-PROJECT-ID': 'NEW_PROJECT_ID'} + sess.delete.assert_called_with(url, endpoint_filter=sot.service, + headers=headers) + sess.get_project_id.assert_called_once_with() + sot._translate_response.assert_called_once_with(resp, has_body=False) + + def test_delete_client_id_project_id_exist(self): + sess = mock.Mock() + resp = mock.Mock() + sess.delete.return_value = resp + + sot = queue.Queue(**FAKE2) + sot._translate_response = mock.Mock() + sot.delete(sess) + + url = 'queues/%s' % FAKE2['name'] + headers = {'Client-ID': 'OLD_CLIENT_ID', + 'X-PROJECT-ID': 'OLD_PROJECT_ID'} + sess.delete.assert_called_with(url, endpoint_filter=sot.service, + headers=headers) + sot._translate_response.assert_called_once_with(resp, has_body=False)