Merge "Add Tests for Message & Claim APIs"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index bf0b308..33e3b31 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -845,6 +845,28 @@
# queues (integer value)
#max_queues_per_page=20
+# The maximum metadata size for a queue (integer value)
+#max_queue_metadata=65536
+
+# The maximum number of queue message per page when listing
+# (or) posting messages (integer value)
+#max_messages_per_page=20
+
+# The maximum size of a message body (integer value)
+#max_message_size=262144
+
+# The maximum number of messages per claim (integer value)
+#max_messages_per_claim=20
+
+# The maximum ttl for a message (integer value)
+#max_message_ttl=1209600
+
+# The maximum ttl for a claim (integer value)
+#max_claim_ttl=43200
+
+# The maximum grace period for a claim (integer value)
+#max_claim_grace=43200
+
[scenario]
diff --git a/tempest/api/queuing/base.py b/tempest/api/queuing/base.py
index 5649619..f4ff7f1 100644
--- a/tempest/api/queuing/base.py
+++ b/tempest/api/queuing/base.py
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
from tempest import test
@@ -89,3 +90,80 @@
"""Wrapper utility that sets the metadata of a queue."""
resp, body = cls.client.set_queue_metadata(queue_name, rbody)
return resp, body
+
+ @classmethod
+ def post_messages(cls, queue_name, rbody):
+ '''Wrapper utility that posts messages to a queue.'''
+ resp, body = cls.client.post_messages(queue_name, rbody)
+
+ return resp, body
+
+ @classmethod
+ def list_messages(cls, queue_name):
+ '''Wrapper utility that lists the messages in a queue.'''
+ resp, body = cls.client.list_messages(queue_name)
+
+ return resp, body
+
+ @classmethod
+ def get_single_message(cls, message_uri):
+ '''Wrapper utility that gets a single message.'''
+ resp, body = cls.client.get_single_message(message_uri)
+
+ return resp, body
+
+ @classmethod
+ def get_multiple_messages(cls, message_uri):
+ '''Wrapper utility that gets multiple messages.'''
+ resp, body = cls.client.get_multiple_messages(message_uri)
+
+ return resp, body
+
+ @classmethod
+ def delete_messages(cls, message_uri):
+ '''Wrapper utility that deletes messages.'''
+ resp, body = cls.client.delete_messages(message_uri)
+
+ return resp, body
+
+ @classmethod
+ def post_claims(cls, queue_name, rbody, url_params=False):
+ '''Wrapper utility that claims messages.'''
+ resp, body = cls.client.post_claims(
+ queue_name, rbody, url_params=False)
+
+ return resp, body
+
+ @classmethod
+ def query_claim(cls, claim_uri):
+ '''Wrapper utility that gets a claim.'''
+ resp, body = cls.client.query_claim(claim_uri)
+
+ return resp, body
+
+ @classmethod
+ def update_claim(cls, claim_uri, rbody):
+ '''Wrapper utility that updates a claim.'''
+ resp, body = cls.client.update_claim(claim_uri, rbody)
+
+ return resp, body
+
+ @classmethod
+ def release_claim(cls, claim_uri):
+ '''Wrapper utility that deletes a claim.'''
+ resp, body = cls.client.release_claim(claim_uri)
+
+ return resp, body
+
+ @classmethod
+ def generate_message_body(cls, repeat=1):
+ '''Wrapper utility that sets the metadata of a queue.'''
+ message_ttl = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_message_ttl)
+
+ key = data_utils.arbitrary_string(size=20, base_text='QueuingKey')
+ value = data_utils.arbitrary_string(size=20, base_text='QueuingValue')
+ message_body = {key: value}
+
+ rbody = ([{'body': message_body, 'ttl': message_ttl}] * repeat)
+ return rbody
diff --git a/tempest/api/queuing/test_claims.py b/tempest/api/queuing/test_claims.py
new file mode 100644
index 0000000..a306623
--- /dev/null
+++ b/tempest/api/queuing/test_claims.py
@@ -0,0 +1,123 @@
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# 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 logging
+import urlparse
+
+from tempest.api.queuing import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class TestClaims(base.BaseQueuingTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestClaims, cls).setUpClass()
+ cls.queue_name = data_utils.rand_name('Queues-Test')
+ # Create Queue
+ cls.create_queue(cls.queue_name)
+
+ def _post_and_claim_messages(self, queue_name, repeat=1):
+ # Post Messages
+ message_body = self.generate_message_body(repeat=repeat)
+ self.client.post_messages(queue_name=self.queue_name,
+ rbody=message_body)
+
+ # Post Claim
+ claim_ttl = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_claim_ttl)
+ claim_grace = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_claim_grace)
+ claim_body = {"ttl": claim_ttl, "grace": claim_grace}
+ resp, body = self.client.post_claims(queue_name=self.queue_name,
+ rbody=claim_body)
+
+ return resp, body
+
+ @test.attr(type='smoke')
+ def test_post_claim(self):
+ _, body = self._post_and_claim_messages(queue_name=self.queue_name)
+ claimed_message_uri = body[0]['href']
+
+ # Skipping this step till bug-1331517 is fixed
+ # Get posted claim
+ # self.client.query_claim(claimed_message_uri)
+
+ # Delete Claimed message
+ self.client.delete_messages(claimed_message_uri)
+
+ @test.skip_because(bug="1331517")
+ @test.attr(type='smoke')
+ def test_query_claim(self):
+ # Post a Claim
+ resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
+
+ # Query Claim
+ claim_uri = resp['location']
+ self.client.query_claim(claim_uri)
+
+ # Delete Claimed message
+ claimed_message_uri = body[0]['href']
+ self.delete_messages(claimed_message_uri)
+
+ @test.skip_because(bug="1328111")
+ @test.attr(type='smoke')
+ def test_update_claim(self):
+ # Post a Claim
+ resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
+
+ claim_uri = resp['location']
+ claimed_message_uri = body[0]['href']
+
+ # Update Claim
+ claim_ttl = data_utils.rand_int_id(start=60,
+ end=CONF.queuing.max_claim_ttl)
+ update_rbody = {"ttl": claim_ttl}
+
+ self.client.update_claim(claim_uri, rbody=update_rbody)
+
+ # Verify claim ttl >= updated ttl value
+ _, body = self.client.query_claim(claim_uri)
+ updated_claim_ttl = body["ttl"]
+ self.assertTrue(updated_claim_ttl >= claim_ttl)
+
+ # Delete Claimed message
+ self.client.delete_messages(claimed_message_uri)
+
+ @test.attr(type='smoke')
+ def test_release_claim(self):
+ # Post a Claim
+ resp, body = self._post_and_claim_messages(queue_name=self.queue_name)
+ claim_uri = resp['location']
+
+ # Release Claim
+ self.client.release_claim(claim_uri)
+
+ # Delete Claimed message
+ # This will implicitly verify that the claim is deleted.
+ message_uri = urlparse.urlparse(claim_uri).path
+ self.client.delete_messages(message_uri)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.delete_queue(cls.queue_name)
+ super(TestClaims, cls).tearDownClass()
diff --git a/tempest/api/queuing/test_messages.py b/tempest/api/queuing/test_messages.py
new file mode 100644
index 0000000..9546c91
--- /dev/null
+++ b/tempest/api/queuing/test_messages.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# 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 logging
+
+from tempest.api.queuing import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class TestMessages(base.BaseQueuingTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestMessages, cls).setUpClass()
+ cls.queue_name = data_utils.rand_name('Queues-Test')
+ # Create Queue
+ cls.client.create_queue(cls.queue_name)
+
+ def _post_messages(self, repeat=CONF.queuing.max_messages_per_page):
+ message_body = self.generate_message_body(repeat=repeat)
+ resp, body = self.post_messages(queue_name=self.queue_name,
+ rbody=message_body)
+ return resp, body
+
+ @test.attr(type='smoke')
+ def test_post_messages(self):
+ # Post Messages
+ resp, _ = self._post_messages()
+
+ # Get on the posted messages
+ message_uri = resp['location']
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_list_messages(self):
+ # Post Messages
+ self._post_messages()
+
+ # List Messages
+ resp, _ = self.list_messages(queue_name=self.queue_name)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_get_message(self):
+ # Post Messages
+ _, body = self._post_messages()
+ message_uri = body['resources'][0]
+
+ # Get posted message
+ resp, _ = self.client.get_single_message(message_uri)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_get_multiple_messages(self):
+ # Post Messages
+ resp, _ = self._post_messages()
+ message_uri = resp['location']
+
+ # Get posted messages
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response cannot be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_delete_single_message(self):
+ # Post Messages
+ _, body = self._post_messages()
+ message_uri = body['resources'][0]
+
+ # Delete posted message & verify the delete operration
+ self.client.delete_messages(message_uri)
+
+ message_uri = message_uri.replace('/messages/', '/messages?ids=')
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response has to be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('204', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_delete_multiple_messages(self):
+ # Post Messages
+ resp, _ = self._post_messages()
+ message_uri = resp['location']
+
+ # Delete multiple messages
+ self.client.delete_messages(message_uri)
+ resp, _ = self.client.get_multiple_messages(message_uri)
+ # The test has an assertion here, because the response has to be 204
+ # in this case (the client allows 200 or 204 for this API call).
+ self.assertEqual('204', resp['status'])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.delete_queue(cls.queue_name)
+ super(TestMessages, cls).tearDownClass()
diff --git a/tempest/api_schema/queuing/v1/queues.py b/tempest/api_schema/queuing/v1/queues.py
index 4630e1c..f0b2691 100644
--- a/tempest/api_schema/queuing/v1/queues.py
+++ b/tempest/api_schema/queuing/v1/queues.py
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
list_link = {
'type': 'object',
'properties': {
@@ -58,6 +59,11 @@
}
}
+age = {
+ 'type': 'number',
+ 'minimum': 0
+}
+
message_link = {
'type': 'object',
'properties': {
@@ -65,7 +71,7 @@
'type': 'string',
'format': 'uri'
},
- 'age': {'type': 'number'},
+ 'age': age,
'created': {
'type': 'string',
'format': 'date-time'
@@ -96,3 +102,136 @@
'required': ['messages']
}
}
+
+resource_schema = {
+ 'type': 'array',
+ 'items': 'string',
+ 'minItems': 1
+}
+
+post_messages = {
+ 'status_code': [201],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'resources': resource_schema,
+ 'partial': {'type': 'boolean'}
+ }
+ },
+ 'required': ['resources', 'partial']
+}
+
+message_ttl = {
+ 'type': 'number',
+ 'minimum': 1
+}
+
+list_messages_links = {
+ 'type': 'array',
+ 'maxItems': 1,
+ 'minItems': 1,
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'rel': {'type': 'string'},
+ 'href': {'type': 'string'}
+ },
+ 'required': ['rel', 'href']
+ }
+}
+
+list_messages_response = {
+ 'type': 'array',
+ 'minItems': 1,
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {'type': 'string'},
+ 'ttl': message_ttl,
+ 'age': age,
+ 'body': {'type': 'object'}
+ },
+ 'required': ['href', 'ttl', 'age', 'body']
+ }
+}
+
+list_messages = {
+ 'status_code': [200, 204],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'links': list_messages_links,
+ 'messages': list_messages_response
+ }
+ },
+ 'required': ['links', 'messages']
+}
+
+single_message = {
+ 'type': 'object',
+ 'properties': {
+ 'href': {'type': 'string'},
+ 'ttl': message_ttl,
+ 'age': age,
+ 'body': {'type': 'object'}
+ },
+ 'required': ['href', 'ttl', 'age', 'body']
+}
+
+get_single_message = {
+ 'status_code': [200],
+ 'response_body': single_message
+}
+
+get_multiple_messages = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'array',
+ 'items': single_message,
+ 'minItems': 1
+ }
+}
+
+messages_claimed = {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'ttl': message_ttl,
+ 'age': {'type': 'number'},
+ 'body': {'type': 'object'}
+ },
+ 'required': ['href', 'ttl', 'age', 'body']
+}
+
+claim_messages = {
+ 'status_code': [201, 204],
+ 'response_body': {
+ 'type': 'array',
+ 'items': messages_claimed,
+ 'minItems': 1
+ }
+}
+
+claim_ttl = {
+ 'type': 'number',
+ 'minimum': 1
+}
+
+query_claim = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'age': {'type': 'number'},
+ 'ttl': claim_ttl,
+ 'messages': {
+ 'type': 'array',
+ 'minItems': 1
+ }
+ },
+ 'required': ['ttl', 'age', 'messages']
+ }
+}
diff --git a/tempest/config.py b/tempest/config.py
index 7d871cb..06c7bc0 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -455,6 +455,28 @@
default=20,
help='The maximum number of queue records per page when '
'listing queues'),
+ cfg.IntOpt('max_queue_metadata',
+ default=65536,
+ help='The maximum metadata size for a queue'),
+ cfg.IntOpt('max_messages_per_page',
+ default=20,
+ help='The maximum number of queue message per page when '
+ 'listing (or) posting messages'),
+ cfg.IntOpt('max_message_size',
+ default=262144,
+ help='The maximum size of a message body'),
+ cfg.IntOpt('max_messages_per_claim',
+ default=20,
+ help='The maximum number of messages per claim'),
+ cfg.IntOpt('max_message_ttl',
+ default=1209600,
+ help='The maximum ttl for a message'),
+ cfg.IntOpt('max_claim_ttl',
+ default=43200,
+ help='The maximum ttl for a claim'),
+ cfg.IntOpt('max_claim_grace',
+ default=43200,
+ help='The maximum grace period for a claim'),
]
volume_group = cfg.OptGroup(name='volume',
diff --git a/tempest/services/queuing/json/queuing_client.py b/tempest/services/queuing/json/queuing_client.py
index e5978f5..031c9c6 100644
--- a/tempest/services/queuing/json/queuing_client.py
+++ b/tempest/services/queuing/json/queuing_client.py
@@ -14,11 +14,14 @@
# limitations under the License.
import json
+import urllib
from tempest.api_schema.queuing.v1 import queues as queues_schema
from tempest.common import rest_client
+from tempest.common.utils import data_utils
from tempest import config
+
CONF = config.CONF
@@ -30,11 +33,16 @@
self.version = '1'
self.uri_prefix = 'v{0}'.format(self.version)
+ client_id = data_utils.rand_uuid_hex()
+ self.headers = {'Client-ID': client_id}
+
def list_queues(self):
uri = '{0}/queues'.format(self.uri_prefix)
resp, body = self.get(uri)
- body = json.loads(body)
- self.validate_response(queues_schema.list_queues, resp, body)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.list_queues, resp, body)
return resp, body
def create_queue(self, queue_name):
@@ -74,3 +82,80 @@
uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
resp, body = self.put(uri, body=json.dumps(rbody))
return resp, body
+
+ def post_messages(self, queue_name, rbody):
+ uri = '{0}/queues/{1}/messages'.format(self.uri_prefix, queue_name)
+ resp, body = self.post(uri, body=json.dumps(rbody),
+ extra_headers=True,
+ headers=self.headers)
+
+ body = json.loads(body)
+ return resp, body
+
+ def list_messages(self, queue_name):
+ uri = '{0}/queues/{1}/messages?echo=True'.format(self.uri_prefix,
+ queue_name)
+ resp, body = self.get(uri, extra_headers=True, headers=self.headers)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.list_messages, resp, body)
+
+ return resp, body
+
+ def get_single_message(self, message_uri):
+ resp, body = self.get(message_uri, extra_headers=True,
+ headers=self.headers)
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.get_single_message, resp,
+ body)
+ return resp, body
+
+ def get_multiple_messages(self, message_uri):
+ resp, body = self.get(message_uri, extra_headers=True,
+ headers=self.headers)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.get_multiple_messages,
+ resp,
+ body)
+
+ return resp, body
+
+ def delete_messages(self, message_uri):
+ resp, body = self.delete(message_uri)
+ assert(resp['status'] == '204')
+ return resp, body
+
+ def post_claims(self, queue_name, rbody, url_params=False):
+ uri = '{0}/queues/{1}/claims'.format(self.uri_prefix, queue_name)
+ if url_params:
+ uri += '?%s' % urllib.urlencode(url_params)
+
+ resp, body = self.post(uri, body=json.dumps(rbody),
+ extra_headers=True,
+ headers=self.headers)
+
+ body = json.loads(body)
+ self.validate_response(queues_schema.claim_messages, resp, body)
+ return resp, body
+
+ def query_claim(self, claim_uri):
+ resp, body = self.get(claim_uri)
+
+ if resp['status'] != '204':
+ body = json.loads(body)
+ self.validate_response(queues_schema.query_claim, resp, body)
+ return resp, body
+
+ def update_claim(self, claim_uri, rbody):
+ resp, body = self.patch(claim_uri, body=json.dumps(rbody))
+ assert(resp['status'] == '204')
+ return resp, body
+
+ def release_claim(self, claim_uri):
+ resp, body = self.delete(claim_uri)
+ assert(resp['status'] == '204')
+ return resp, body