Merge "Remove supporto for V1.1 APIs from Zaqar 2"

This commit is contained in:
Zuul
2025-07-09 17:16:56 +00:00
committed by Gerrit Code Review
13 changed files with 5 additions and 1947 deletions

View File

@@ -19,4 +19,3 @@ from zaqar.tests.unit.transport.wsgi import base
TestBase = base.TestBase
TestBaseFaulty = base.TestBaseFaulty
V1_1Base = base.V1_1Base

View File

@@ -136,34 +136,18 @@ class TestBaseFaulty(TestBase):
"""This test ensures we aren't letting any exceptions go unhandled."""
class V1_1Base(TestBase):
"""Base class for V1.1 API Tests.
Should contain methods specific to V1.1 of the API
"""
url_prefix = '/v1.1'
def _empty_message_list(self, body):
self.assertEqual([], jsonutils.loads(body[0])['messages'])
class V1_1BaseFaulty(TestBaseFaulty):
"""Base class for V1.1 API Faulty Tests.
Should contain methods specific to V1.1 exception testing
"""
url_prefix = '/v1.1'
class V2Base(V1_1Base):
class V2Base(TestBase):
"""Base class for V2 API Tests.
Should contain methods specific to V2 of the API
"""
url_prefix = '/v2'
def _empty_message_list(self, body):
self.assertEqual([], jsonutils.loads(body[0])['messages'])
class V2BaseFaulty(V1_1BaseFaulty):
class V2BaseFaulty(TestBaseFaulty):
"""Base class for V2 API Faulty Tests.
Should contain methods specific to V2 exception testing

View File

@@ -1,43 +0,0 @@
# Copyright (c) 2013 Red Hat, 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.
"""Test Auth."""
import falcon
from falcon import testing
from keystonemiddleware import auth_token
from oslo_utils import uuidutils
from zaqar.tests.unit.transport.wsgi import base
class TestAuth(base.V1_1Base):
config_file = 'keystone_auth.conf'
def setUp(self):
super(TestAuth, self).setUp()
self.headers = {'Client-ID': uuidutils.generate_uuid()}
def test_auth_install(self):
self.assertIsInstance(self.app._auth_app, auth_token.AuthProtocol)
def test_non_authenticated(self):
env = testing.create_environ(self.url_prefix + '/480924/queues/',
method='GET',
headers=self.headers)
self.app(env, self.srmock)
self.assertEqual(falcon.HTTP_401, self.srmock.status)

View File

@@ -1,314 +0,0 @@
# Copyright (c) 2013 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 datetime
from unittest import mock
import ddt
import falcon
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from oslo_utils import uuidutils
from testtools import matchers
from zaqar import tests as testing
from zaqar.tests.unit.transport.wsgi import base
@ddt.ddt
class TestClaimsMongoDB(base.V1_1Base):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestClaimsMongoDB, self).setUp()
self.default_claim_ttl = self.boot.transport._defaults.claim_ttl
self.project_id = '737_abc8332832'
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': self.project_id
}
self.queue_path = self.url_prefix + '/queues/fizbit'
self.claims_path = self.queue_path + '/claims'
self.messages_path = self.queue_path + '/messages'
doc = jsonutils.dumps({"_ttl": 60})
self.simulate_put(self.queue_path, body=doc, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
doc = jsonutils.dumps({'messages': [{'body': 239, 'ttl': 300}] * 10})
self.simulate_post(self.queue_path + '/messages',
body=doc, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
def tearDown(self):
storage = self.boot.storage._storage
control = self.boot.control
connection = storage.connection
connection.drop_database(control.queues_database)
for db in storage.message_databases:
connection.drop_database(db)
self.simulate_delete(self.queue_path, headers=self.headers)
super(TestClaimsMongoDB, self).tearDown()
@ddt.data('[', '[]', '.', '"fail"')
def test_bad_claim(self, doc):
self.simulate_post(self.claims_path, body=doc, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
href = self._get_a_claim()
self.simulate_patch(href, body=doc, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_exceeded_claim(self):
self.simulate_post(self.claims_path,
body='{"ttl": 100, "grace": 60}',
query_string='limit=21', headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
@ddt.data((-1, -1), (59, 60), (60, 59), (60, 43201), (43201, 60))
def test_unacceptable_ttl_or_grace(self, ttl_grace):
ttl, grace = ttl_grace
self.simulate_post(self.claims_path,
body=jsonutils.dumps({'ttl': ttl, 'grace': grace}),
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
@ddt.data(-1, 59, 43201)
def test_unacceptable_new_ttl(self, ttl):
href = self._get_a_claim()
self.simulate_patch(href,
body=jsonutils.dumps({'ttl': ttl}),
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_default_ttl_and_grace(self):
self.simulate_post(self.claims_path,
body='{}', headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
body = self.simulate_get(self.srmock.headers_dict['location'],
headers=self.headers)
claim = jsonutils.loads(body[0])
self.assertEqual(self.default_claim_ttl, claim['ttl'])
def _get_a_claim(self):
doc = '{"ttl": 100, "grace": 60}'
self.simulate_post(self.claims_path, body=doc, headers=self.headers)
return self.srmock.headers_dict['Location']
def test_lifecycle(self):
doc = '{"ttl": 100, "grace": 60}'
# First, claim some messages
body = self.simulate_post(self.claims_path, body=doc,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
claimed = jsonutils.loads(body[0])['messages']
claim_href = self.srmock.headers_dict['Location']
message_href, params = claimed[0]['href'].split('?')
# No more messages to claim
self.simulate_post(self.claims_path, body=doc,
query_string='limit=3', headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Listing messages, by default, won't include claimed, will echo
body = self.simulate_get(self.messages_path,
headers=self.headers,
query_string="echo=true")
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(body)
# Listing messages, by default, won't include claimed, won't echo
body = self.simulate_get(self.messages_path,
headers=self.headers,
query_string="echo=false")
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(body)
# List messages, include_claimed, but don't echo
body = self.simulate_get(self.messages_path,
query_string='include_claimed=true'
'&echo=false',
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(body)
# List messages with a different client-id and echo=false.
# Should return some messages
headers = self.headers.copy()
headers["Client-ID"] = uuidutils.generate_uuid()
body = self.simulate_get(self.messages_path,
query_string='include_claimed=true'
'&echo=false',
headers=headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Include claimed messages this time, and echo
body = self.simulate_get(self.messages_path,
query_string='include_claimed=true'
'&echo=true',
headers=self.headers)
listed = jsonutils.loads(body[0])
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self.assertEqual(len(claimed), len(listed['messages']))
now = timeutils.utcnow() + datetime.timedelta(seconds=10)
timeutils_utcnow = 'oslo_utils.timeutils.utcnow'
with mock.patch(timeutils_utcnow) as mock_utcnow:
mock_utcnow.return_value = now
body = self.simulate_get(claim_href, headers=self.headers)
claim = jsonutils.loads(body[0])
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self.assertEqual(100, claim['ttl'])
# NOTE(cpp-cabrera): verify that claim age is non-negative
self.assertThat(claim['age'], matchers.GreaterThan(-1))
# Try to delete the message without submitting a claim_id
self.simulate_delete(message_href, headers=self.headers)
self.assertEqual(falcon.HTTP_403, self.srmock.status)
# Delete the message and its associated claim
self.simulate_delete(message_href,
query_string=params, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Try to get it from the wrong project
headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': 'bogusproject'
}
self.simulate_get(message_href, query_string=params, headers=headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
# Get the message
self.simulate_get(message_href, query_string=params,
headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
# Update the claim
new_claim_ttl = '{"ttl": 60, "grace": 60}'
creation = timeutils.utcnow()
self.simulate_patch(claim_href, body=new_claim_ttl,
headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Get the claimed messages (again)
body = self.simulate_get(claim_href, headers=self.headers)
query = timeutils.utcnow()
claim = jsonutils.loads(body[0])
message_href, params = claim['messages'][0]['href'].split('?')
self.assertEqual(60, claim['ttl'])
estimated_age = timeutils.delta_seconds(creation, query)
self.assertGreater(estimated_age, claim['age'])
# Delete the claim
self.simulate_delete(claim['href'], headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Try to delete a message with an invalid claim ID
self.simulate_delete(message_href,
query_string=params, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
# Make sure it wasn't deleted!
self.simulate_get(message_href, query_string=params,
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Try to get a claim that doesn't exist
self.simulate_get(claim['href'], headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
# Try to update a claim that doesn't exist
self.simulate_patch(claim['href'], body=doc,
headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
def test_post_claim_nonexistent_queue(self):
path = self.url_prefix + '/queues/nonexistent/claims'
self.simulate_post(path,
body='{"ttl": 100, "grace": 60}',
headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
def test_get_claim_nonexistent_queue(self):
path = self.url_prefix + '/queues/nonexistent/claims/aaabbbba'
self.simulate_get(path, headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
# NOTE(cpp-cabrera): regression test against bug #1203842
def test_get_nonexistent_claim_404s(self):
self.simulate_get(self.claims_path + '/a', headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
def test_delete_nonexistent_claim_204s(self):
self.simulate_delete(self.claims_path + '/a',
headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
def test_patch_nonexistent_claim_404s(self):
patch_data = jsonutils.dumps({'ttl': 100})
self.simulate_patch(self.claims_path + '/a', body=patch_data,
headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
class TestClaimsFaultyDriver(base.V1_1BaseFaulty):
config_file = 'wsgi_faulty.conf'
def test_simple(self):
self.project_id = '480924abc_'
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': self.project_id
}
claims_path = self.url_prefix + '/queues/fizbit/claims'
doc = '{"ttl": 100, "grace": 60}'
self.simulate_post(claims_path, body=doc, headers=self.headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_get(claims_path + '/nichts', headers=self.headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_patch(claims_path + '/nichts', body=doc,
headers=self.headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_delete(claims_path + '/foo', headers=self.headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)

View File

@@ -1,124 +0,0 @@
# Copyright (c) 2013 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 contextlib
import falcon
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from zaqar import storage
from zaqar.tests.unit.transport.wsgi import base
class TestDefaultLimits(base.V1_1Base):
config_file = 'wsgi_mongodb_default_limits.conf'
def setUp(self):
super(TestDefaultLimits, self).setUp()
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': '%s_' % uuidutils.generate_uuid()
}
self.queue_path = self.url_prefix + '/queues'
self.q1_queue_path = self.queue_path + '/' + uuidutils.generate_uuid()
self.messages_path = self.q1_queue_path + '/messages'
self.claims_path = self.q1_queue_path + '/claims'
self.simulate_put(self.q1_queue_path, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
def tearDown(self):
self.simulate_delete(self.queue_path, headers=self.headers)
super(TestDefaultLimits, self).tearDown()
def test_queue_listing(self):
# 2 queues to list
self.simulate_put(self.queue_path + '/q2', headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
with self._prepare_queues(storage.DEFAULT_QUEUES_PER_PAGE + 1):
result = self.simulate_get(self.queue_path, headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
queues = jsonutils.loads(result[0])['queues']
self.assertEqual(storage.DEFAULT_QUEUES_PER_PAGE, len(queues))
def test_message_listing_different_id(self):
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_PAGE + 1)
headers = self.headers.copy()
headers['Client-ID'] = uuidutils.generate_uuid()
result = self.simulate_get(self.messages_path,
headers=headers,
query_string='echo=false')
self.assertEqual(falcon.HTTP_200, self.srmock.status)
messages = jsonutils.loads(result[0])['messages']
self.assertEqual(storage.DEFAULT_MESSAGES_PER_PAGE, len(messages))
def test_message_listing_same_id(self):
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_PAGE + 1)
result = self.simulate_get(self.messages_path,
headers=self.headers,
query_string='echo=false')
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(result)
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_PAGE + 1)
result = self.simulate_get(self.messages_path,
headers=self.headers,
query_string='echo=true')
messages = jsonutils.loads(result[0])['messages']
self.assertEqual(storage.DEFAULT_MESSAGES_PER_PAGE, len(messages))
def test_claim_creation(self):
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_CLAIM + 1)
result = self.simulate_post(self.claims_path,
body='{"ttl": 60, "grace": 60}',
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
messages = jsonutils.loads(result[0])['messages']
self.assertEqual(storage.DEFAULT_MESSAGES_PER_CLAIM, len(messages))
@contextlib.contextmanager
def _prepare_queues(self, count):
queue_paths = [self.queue_path + '/multi-{0}'.format(i)
for i in range(count)]
for path in queue_paths:
self.simulate_put(path, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
yield
for path in queue_paths:
self.simulate_delete(path, headers=self.headers)
def _prepare_messages(self, count):
doc = {'messages': [{'body': 239, 'ttl': 300}] * count}
body = jsonutils.dumps(doc)
self.simulate_post(self.messages_path, body=body,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)

View File

@@ -1,90 +0,0 @@
# Copyright 2014 Catalyst IT Ltd
#
# 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 unittest import mock
import ddt
import falcon
from oslo_serialization import jsonutils
from zaqar.storage import errors
import zaqar.storage.mongodb as mongo
from zaqar import tests as testing
from zaqar.tests.unit.transport.wsgi import base
@ddt.ddt
class TestHealthMongoDB(base.V1_1Base):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestHealthMongoDB, self).setUp()
def test_basic(self):
path = self.url_prefix + '/health'
body = self.simulate_get(path)
health = jsonutils.loads(body[0])
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self.assertTrue(health['storage_reachable'])
self.assertIsNotNone(health['message_volume'])
for op in health['operation_status']:
self.assertTrue(health['operation_status'][op]['succeeded'])
@mock.patch.object(mongo.driver.DataDriver, '_health')
def test_message_volume(self, mock_driver_get):
def _health():
KPI = {}
KPI['message_volume'] = {'free': 1, 'claimed': 2, 'total': 3}
return KPI
mock_driver_get.side_effect = _health
path = self.url_prefix + '/health'
body = self.simulate_get(path)
health = jsonutils.loads(body[0])
self.assertEqual(falcon.HTTP_200, self.srmock.status)
message_volume = health['message_volume']
self.assertEqual(1, message_volume['free'])
self.assertEqual(2, message_volume['claimed'])
self.assertEqual(3, message_volume['total'])
@mock.patch.object(mongo.messages.MessageController, 'delete')
def test_operation_status(self, mock_messages_delete):
mock_messages_delete.side_effect = errors.NotPermitted()
path = self.url_prefix + '/health'
body = self.simulate_get(path)
health = jsonutils.loads(body[0])
self.assertEqual(falcon.HTTP_200, self.srmock.status)
op_status = health['operation_status']
for op in op_status.keys():
if op == 'delete_messages':
self.assertFalse(op_status[op]['succeeded'])
self.assertIsNotNone(op_status[op]['ref'])
else:
self.assertTrue(op_status[op]['succeeded'])
class TestHealthFaultyDriver(base.V1_1BaseFaulty):
config_file = 'wsgi_faulty.conf'
def test_simple(self):
path = self.url_prefix + '/health'
self.simulate_get(path)
self.assertEqual(falcon.HTTP_503, self.srmock.status)

View File

@@ -1,71 +0,0 @@
# Copyright (c) 2013 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 falcon
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from urllib import parse as urlparse
from zaqar.tests.unit.transport.wsgi import base
class TestHomeDocument(base.V1_1Base):
config_file = 'wsgi_mongodb.conf'
def test_json_response(self):
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': '8383830383abc_'
}
body = self.simulate_get(self.url_prefix + '/', headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
content_type = self.srmock.headers_dict['Content-Type']
self.assertEqual('application/json-home', content_type)
try:
jsonutils.loads(body[0])
except ValueError:
self.fail('Home document is not valid JSON')
def test_href_template(self):
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': '8383830383'
}
body = self.simulate_get(self.url_prefix + '/', headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
resp = jsonutils.loads(body[0])
queue_href_template = resp['resources']['rel/queue']['href-template']
path_1 = 'https://zaqar.example.com' + self.url_prefix
path_2 = 'https://zaqar.example.com' + self.url_prefix + '/'
# Verify all the href template start with the correct version prefix
def get_href_or_template(resource):
return resource.get('href-template', '') or resource['href']
for resource in list(resp['resources']):
self.assertTrue(
get_href_or_template(resp['resources'][resource]).
startswith(self.url_prefix))
url = urlparse.urljoin(path_1, queue_href_template)
expected = ('https://zaqar.example.com' + self.url_prefix +
'/queues/foo')
self.assertEqual(expected, url.format(queue_name='foo'))
url = urlparse.urljoin(path_2, queue_href_template)
self.assertEqual(expected, url.format(queue_name='foo'))

View File

@@ -1,81 +0,0 @@
# Copyright (c) 2013 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 uuid
import falcon
from falcon import testing
from oslo_serialization import jsonutils
from zaqar.tests.unit.transport.wsgi import base
class TestMediaType(base.V1_1Base):
config_file = 'wsgi_mongodb.conf'
def test_json_only_endpoints_with_wrong_accept_header(self):
endpoints = (
('GET', self.url_prefix + '/queues'),
('GET', self.url_prefix + '/queues/nonexistent/stats'),
('POST', self.url_prefix + '/queues/nonexistent/messages'),
('GET', self.url_prefix + '/queues/nonexistent/messages/deadbeaf'),
('POST', self.url_prefix + '/queues/nonexistent/claims'),
('GET', self.url_prefix + '/queues/nonexistent/claims/0ad'),
('GET', self.url_prefix + '/health'),
)
for method, endpoint in endpoints:
headers = {
'Client-ID': str(uuid.uuid4()),
'Accept': 'application/xml',
}
env = testing.create_environ(endpoint,
method=method,
headers=headers)
self.app(env, self.srmock)
self.assertEqual(falcon.HTTP_406, self.srmock.status)
def test_request_with_body_and_urlencoded_contenttype_header_fails(self):
# NOTE(Eva-i): this test case makes sure wsgi 'before' hook
# "require_content_type_be_non_urlencoded" works to prevent
# bug/1547100.
eww_queue_path = self.url_prefix + '/queues/eww'
eww_queue_messages_path = eww_queue_path + '/messages'
sample_message = jsonutils.dumps({'messages': [{'body': {'eww!'},
'ttl': 200}]})
bad_headers = {
'Client-ID': str(uuid.uuid4()),
'Content-Type': 'application/x-www-form-urlencoded',
}
# Create queue request with bad headers. Should still work, because it
# has no body.
self.simulate_put(eww_queue_path, headers=bad_headers)
self.addCleanup(self.simulate_delete, eww_queue_path,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
# Post message request with good headers. Should work.
self.simulate_post(eww_queue_messages_path, body=sample_message,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
# Post message request with bad headers. Should not work.
self.simulate_post(eww_queue_messages_path, body=sample_message,
headers=bad_headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)

View File

@@ -1,639 +0,0 @@
# Copyright (c) 2013 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 datetime
from unittest import mock
import uuid
import ddt
import falcon
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from testtools import matchers
from zaqar import tests as testing
from zaqar.tests.unit.transport.wsgi import base
from zaqar.transport import validation
@ddt.ddt
class TestMessagesMongoDB(base.V1_1Base):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestMessagesMongoDB, self).setUp()
self.default_message_ttl = self.boot.transport._defaults.message_ttl
if self.conf.pooling:
for i in range(4):
uri = "%s/%s" % (self.mongodb_url, str(i))
doc = {'weight': 100, 'uri': uri}
self.simulate_put(self.url_prefix + '/pools/' + str(i),
body=jsonutils.dumps(doc))
self.assertEqual(falcon.HTTP_201, self.srmock.status)
self.project_id = '7e55e1a7e'
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': self.project_id
}
# TODO(kgriffs): Add support in self.simulate_* for a "base path"
# so that we don't have to concatenate against self.url_prefix
# all over the place.
self.queue_path = self.url_prefix + '/queues/fizbit'
self.messages_path = self.queue_path + '/messages'
doc = '{"_ttl": 60}'
self.simulate_put(self.queue_path, body=doc, headers=self.headers)
def tearDown(self):
self.simulate_delete(self.queue_path, headers=self.headers)
if self.conf.pooling:
for i in range(4):
self.simulate_delete(self.url_prefix + '/pools/' + str(i),
headers=self.headers)
super(TestMessagesMongoDB, self).tearDown()
def test_name_restrictions(self):
sample_messages = [
{'body': {'key': 'value'}, 'ttl': 200},
]
messages_path = self.url_prefix + '/queues/%s/messages'
sample_doc = jsonutils.dumps({'messages': sample_messages})
self.simulate_post(messages_path % 'Nice-Boat_2',
body=sample_doc, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
self.simulate_post(messages_path % 'Nice-Bo@t',
body=sample_doc, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_post(messages_path % ('_niceboat' * 8),
body=sample_doc, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def _test_post(self, sample_messages):
sample_doc = jsonutils.dumps({'messages': sample_messages})
result = self.simulate_post(self.messages_path,
body=sample_doc, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
result_doc = jsonutils.loads(result[0])
msg_ids = self._get_msg_ids(self.srmock.headers_dict)
self.assertEqual(len(sample_messages), len(msg_ids))
expected_resources = [str(self.messages_path + '/' + id)
for id in msg_ids]
self.assertEqual(expected_resources, result_doc['resources'])
# NOTE(kgriffs): As of v1.1, "partial" is no longer given
# in the response document.
self.assertNotIn('partial', result_doc)
self.assertEqual(len(sample_messages), len(msg_ids))
lookup = dict([(m['ttl'], m['body']) for m in sample_messages])
# Test GET on the message resource directly
# NOTE(cpp-cabrera): force the passing of time to age a message
timeutils_utcnow = 'oslo_utils.timeutils.utcnow'
now = timeutils.utcnow() + datetime.timedelta(seconds=10)
with mock.patch(timeutils_utcnow) as mock_utcnow:
mock_utcnow.return_value = now
for msg_id in msg_ids:
message_uri = self.messages_path + '/' + msg_id
headers = self.headers.copy()
headers['X-Project-ID'] = '777777'
# Wrong project ID
self.simulate_get(message_uri, headers=headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
# Correct project ID
result = self.simulate_get(message_uri, headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Check message properties
message = jsonutils.loads(result[0])
self.assertEqual(message_uri, message['href'])
self.assertEqual(lookup[message['ttl']], message['body'])
self.assertEqual(msg_id, message['id'])
# no negative age
# NOTE(cpp-cabrera): testtools lacks GreaterThanEqual on py26
self.assertThat(message['age'],
matchers.GreaterThan(-1))
# Test bulk GET
query_string = 'ids=' + ','.join(msg_ids)
result = self.simulate_get(self.messages_path,
query_string=query_string,
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
result_doc = jsonutils.loads(result[0])
expected_ttls = set(m['ttl'] for m in sample_messages)
actual_ttls = set(m['ttl'] for m in result_doc['messages'])
self.assertFalse(expected_ttls - actual_ttls)
actual_ids = set(m['id'] for m in result_doc['messages'])
self.assertFalse(set(msg_ids) - actual_ids)
def test_exceeded_payloads(self):
# Get a valid message id
self._post_messages(self.messages_path)
msg_id = self._get_msg_id(self.srmock.headers_dict)
# Bulk GET restriction
query_string = 'ids=' + ','.join([msg_id] * 21)
self.simulate_get(self.messages_path,
query_string=query_string, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
# Listing restriction
self.simulate_get(self.messages_path,
query_string='limit=21',
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
# Bulk deletion restriction
query_string = 'ids=' + ','.join([msg_id] * 22)
self.simulate_delete(self.messages_path,
query_string=query_string, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_post_single(self):
sample_messages = [
{'body': {'key': 'value'}, 'ttl': 200},
]
self._test_post(sample_messages)
def test_post_multiple(self):
sample_messages = [
{'body': 239, 'ttl': 100},
{'body': {'key': 'value'}, 'ttl': 200},
{'body': [1, 3], 'ttl': 300},
]
self._test_post(sample_messages)
def test_post_optional_ttl(self):
sample_messages = {
'messages': [
{'body': 239},
{'body': {'key': 'value'}, 'ttl': 200},
],
}
# Manually check default TTL is max from config
sample_doc = jsonutils.dumps(sample_messages)
result = self.simulate_post(self.messages_path,
body=sample_doc, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
result_doc = jsonutils.loads(result[0])
href = result_doc['resources'][0]
result = self.simulate_get(href, headers=self.headers)
message = jsonutils.loads(result[0])
self.assertEqual(self.default_message_ttl, message['ttl'])
def test_post_to_non_ascii_queue(self):
# NOTE(kgriffs): This test verifies that routes with
# embedded queue name params go through the validation
# hook, regardless of the target resource.
path = self.url_prefix + '/queues/non-ascii-n\u0153me/messages'
self._post_messages(path)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_post_with_long_queue_name(self):
# NOTE(kgriffs): This test verifies that routes with
# embedded queue name params go through the validation
# hook, regardless of the target resource.
queues_path = self.url_prefix + '/queues/'
game_title = 'v' * validation.QUEUE_NAME_MAX_LEN
self.addCleanup(
self.simulate_delete, queues_path + game_title,
headers=self.headers)
self._post_messages(queues_path + game_title + '/messages')
self.assertEqual(falcon.HTTP_201, self.srmock.status)
game_title += 'v'
self._post_messages(queues_path + game_title + '/messages')
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_post_to_missing_queue(self):
self.addCleanup(
self.simulate_delete, self.url_prefix + '/queues/nonexistent',
headers=self.headers)
self._post_messages(self.url_prefix + '/queues/nonexistent/messages')
self.assertEqual(falcon.HTTP_201, self.srmock.status)
def test_get_from_missing_queue(self):
body = self.simulate_get(self.url_prefix +
'/queues/nonexistent/messages',
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(body)
@ddt.data('', '0xdeadbeef', '550893e0-2b6e-11e3-835a-5cf9dd72369')
def test_bad_client_id(self, text_id):
self.simulate_post(self.queue_path + '/messages',
body='{"ttl": 60, "body": ""}',
headers={'Client-ID': text_id})
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_get(self.queue_path + '/messages',
query_string='limit=3&echo=true',
headers={'Client-ID': text_id})
self.assertEqual(falcon.HTTP_400, self.srmock.status)
@ddt.data(None, '[', '[]', '{}', '.')
def test_post_bad_message(self, document):
self.simulate_post(self.queue_path + '/messages',
body=document,
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
@ddt.data(-1, 59, 1209601)
def test_unacceptable_ttl(self, ttl):
doc = {'messages': [{'ttl': ttl, 'body': None}]}
self.simulate_post(self.queue_path + '/messages',
body=jsonutils.dumps(doc),
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_exceeded_message_posting(self):
# Total (raw request) size
doc = {'messages': [{'body': "some body", 'ttl': 100}] * 20}
body = jsonutils.dumps(doc, indent=4)
max_len = self.transport_cfg.max_messages_post_size
long_body = body + (' ' * (max_len - len(body) + 1))
self.simulate_post(self.queue_path + '/messages',
body=long_body,
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
@ddt.data('{"overflow": 9223372036854775808}',
'{"underflow": -9223372036854775809}')
def test_unsupported_json(self, document):
self.simulate_post(self.queue_path + '/messages',
body=document,
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_delete(self):
self._post_messages(self.messages_path)
msg_id = self._get_msg_id(self.srmock.headers_dict)
target = self.messages_path + '/' + msg_id
self.simulate_get(target, headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self.simulate_delete(target, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.simulate_get(target, headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
# Safe to delete non-existing ones
self.simulate_delete(target, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
def test_bulk_delete(self):
path = self.queue_path + '/messages'
self._post_messages(path, repeat=5)
[target, params] = self.srmock.headers_dict['location'].split('?')
# Deleting the whole collection is denied
self.simulate_delete(path, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_delete(target, query_string=params, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.simulate_get(target, query_string=params, headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
# Safe to delete non-existing ones
self.simulate_delete(target, query_string=params, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Even after the queue is gone
self.simulate_delete(self.queue_path, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.simulate_delete(target, query_string=params, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
def test_list(self):
path = self.queue_path + '/messages'
self._post_messages(path, repeat=10)
query_string = 'limit=3&echo=true'
body = self.simulate_get(path,
query_string=query_string,
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
cnt = 0
while jsonutils.loads(body[0])['messages'] != []:
contents = jsonutils.loads(body[0])
[target, params] = contents['links'][0]['href'].split('?')
for msg in contents['messages']:
self.simulate_get(msg['href'], headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
body = self.simulate_get(target,
query_string=params,
headers=self.headers)
cnt += 1
self.assertEqual(4, cnt)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(body)
# Stats
body = self.simulate_get(self.queue_path + '/stats',
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
message_stats = jsonutils.loads(body[0])['messages']
# NOTE(kgriffs): The other parts of the stats are tested
# in tests.storage.base and so are not repeated here.
expected_pattern = self.queue_path + '/messages/[^/]+$'
for message_stat_name in ('oldest', 'newest'):
self.assertThat(message_stats[message_stat_name]['href'],
matchers.MatchesRegex(expected_pattern))
# NOTE(kgriffs): Try to get messages for a missing queue
body = self.simulate_get(self.url_prefix +
'/queues/nonexistent/messages',
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(body)
def test_list_with_bad_marker(self):
path = self.queue_path + '/messages'
self._post_messages(path, repeat=5)
query_string = 'limit=3&echo=true&marker=sfhlsfdjh2048'
body = self.simulate_get(path,
query_string=query_string,
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self._empty_message_list(body)
def test_no_uuid(self):
headers = {
'Client-ID': "textid",
'X-Project-ID': '7e7e7e'
}
path = self.queue_path + '/messages'
self.simulate_post(path, body='[{"body": 0, "ttl": 100}]',
headers=headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_get(path, headers=headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_get_claimed_contains_claim_id_in_href(self):
path = self.queue_path
res = self._post_messages(path + '/messages', repeat=5)
for url in jsonutils.loads(res[0])['resources']:
message = self.simulate_get(url)
self.assertNotIn('claim_id', jsonutils.loads(message[0])['href'])
self.simulate_post(path + '/claims',
body='{"ttl": 100, "grace": 100}',
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
for url in jsonutils.loads(res[0])['resources']:
message = self.simulate_get(url)
self.assertIn('claim_id', jsonutils.loads(message[0])['href'])
# NOTE(cpp-cabrera): regression test against bug #1210633
def test_when_claim_deleted_then_messages_unclaimed(self):
path = self.queue_path
self._post_messages(path + '/messages', repeat=5)
# post claim
self.simulate_post(path + '/claims',
body='{"ttl": 100, "grace": 100}',
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
location = self.srmock.headers_dict['location']
# release claim
self.simulate_delete(location, headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# get unclaimed messages
self.simulate_get(path + '/messages',
query_string='echo=true',
headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# NOTE(cpp-cabrera): regression test against bug #1203842
def test_get_nonexistent_message_404s(self):
path = self.url_prefix + '/queues/notthere/messages/a'
self.simulate_get(path, headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
def test_get_multiple_invalid_messages_404s(self):
path = self.url_prefix + '/queues/notthere/messages'
self.simulate_get(path, query_string='ids=a,b,c',
headers=self.headers)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
def test_delete_multiple_invalid_messages_204s(self):
path = self.url_prefix + '/queues/notthere/messages'
self.simulate_delete(path, query_string='ids=a,b,c',
headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
def test_delete_message_with_invalid_claim_doesnt_delete_message(self):
path = self.queue_path
resp = self._post_messages(path + '/messages', 1)
location = jsonutils.loads(resp[0])['resources'][0]
self.simulate_delete(location, query_string='claim_id=invalid',
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_get(location, headers=self.headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
def test_no_duplicated_messages_path_in_href(self):
"""Test for bug 1240897."""
path = self.queue_path + '/messages'
self._post_messages(path, repeat=1)
msg_id = self._get_msg_id(self.srmock.headers_dict)
query_string = 'ids=%s' % msg_id
body = self.simulate_get(path,
query_string=query_string,
headers=self.headers)
messages = jsonutils.loads(body[0])
self.assertNotIn(self.queue_path + '/messages/messages',
messages['messages'][0]['href'])
def _post_messages(self, target, repeat=1):
doc = {'messages': [{'body': 239, 'ttl': 300}] * repeat}
body = jsonutils.dumps(doc)
return self.simulate_post(target, body=body, headers=self.headers)
def _get_msg_id(self, headers):
return self._get_msg_ids(headers)[0]
def _get_msg_ids(self, headers):
return headers['location'].rsplit('=', 1)[-1].split(',')
@ddt.data(1, 2, 10)
def test_pop(self, message_count):
self._post_messages(self.messages_path, repeat=message_count)
msg_id = self._get_msg_id(self.srmock.headers_dict)
target = self.messages_path + '/' + msg_id
self.simulate_get(target, self.project_id)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
query_string = 'pop=' + str(message_count)
result = self.simulate_delete(self.messages_path, self.project_id,
query_string=query_string)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
result_doc = jsonutils.loads(result[0])
self.assertEqual(message_count, len(result_doc['messages']))
self.simulate_get(target, self.project_id)
self.assertEqual(falcon.HTTP_404, self.srmock.status)
@ddt.data('', 'pop=1000000', 'pop=10&ids=1', 'pop=-1')
def test_pop_invalid(self, query_string):
self.simulate_delete(self.messages_path, self.project_id,
query_string=query_string)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_pop_empty_queue(self):
query_string = 'pop=1'
result = self.simulate_delete(self.messages_path, self.project_id,
query_string=query_string)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
result_doc = jsonutils.loads(result[0])
self.assertEqual([], result_doc['messages'])
def test_pop_single_message(self):
self._post_messages(self.messages_path, repeat=5)
msg_id = self._get_msg_id(self.srmock.headers_dict)
target = self.messages_path + '/' + msg_id
self.simulate_get(target, self.project_id)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Pop Single message from the queue
query_string = 'pop=1'
result = self.simulate_delete(self.messages_path, self.project_id,
query_string=query_string)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Get messages from the queue & verify message count
query_string = 'echo=True'
result = self.simulate_get(self.messages_path, self.project_id,
query_string=query_string,
headers=self.headers)
result_doc = jsonutils.loads(result[0])
actual_msg_count = len(result_doc['messages'])
expected_msg_count = 4
self.assertEqual(expected_msg_count, actual_msg_count)
class TestMessagesMongoDBPooled(TestMessagesMongoDB):
config_file = 'wsgi_mongodb_pooled.conf'
# TODO(cpp-cabrera): remove this skipTest once pooled queue
# listing is implemented
def test_list(self):
self.skipTest("Need to implement pooled queue listing.")
class TestMessagesFaultyDriver(base.V1_1BaseFaulty):
config_file = 'wsgi_faulty.conf'
def test_simple(self):
project_id = 'xyz'
path = self.url_prefix + '/queues/fizbit/messages'
body = '{"messages": [{"body": 239, "ttl": 100}]}'
headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': project_id
}
self.simulate_post(path,
body=body,
headers=headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_get(path,
headers=headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_get(path + '/nonexistent', headers=headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_delete(path + '/nada', headers=headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)

View File

@@ -1,38 +0,0 @@
# Copyright (c) 2015 Red Hat, 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 falcon
from zaqar.tests.unit.transport.wsgi import base
class TestPing(base.V1_1Base):
config_file = 'wsgi_mongodb.conf'
def test_get(self):
# TODO(kgriffs): Make use of setUp for setting the URL prefix
# so we can just say something like:
#
# response = self.simulate_get('/ping')
#
response = self.simulate_get('/v1.1/ping')
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual([], response)
def test_head(self):
response = self.simulate_head('/v1.1/ping')
self.assertEqual(falcon.HTTP_204, self.srmock.status)
self.assertEqual([], response)

View File

@@ -1,388 +0,0 @@
# Copyright (c) 2013 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.
from unittest import mock
import ddt
import falcon
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from zaqar.storage import errors as storage_errors
from zaqar import tests as testing
from zaqar.tests.unit.transport.wsgi import base
@ddt.ddt
class TestQueueLifecycleMongoDB(base.V1_1Base):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestQueueLifecycleMongoDB, self).setUp()
self.queue_path = self.url_prefix + '/queues'
self.gumshoe_queue_path = self.queue_path + '/gumshoe'
self.fizbat_queue_path = self.queue_path + '/fizbat'
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': '3387309841abc_'
}
def tearDown(self):
storage = self.boot.storage._storage
connection = storage.connection
connection.drop_database(self.boot.control.queues_database)
for db in storage.message_databases:
connection.drop_database(db)
super(TestQueueLifecycleMongoDB, self).tearDown()
def test_empty_project_id(self):
headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': ''
}
self.simulate_put(self.gumshoe_queue_path, headers=headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_delete(self.gumshoe_queue_path, headers=headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
@ddt.data('480924', 'foo')
def test_basics_thoroughly(self, project_id):
headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': project_id
}
gumshoe_queue_path_stats = self.gumshoe_queue_path + '/stats'
# Stats are empty - queue not created yet
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Create
doc = '{"messages": {"ttl": 600}}'
self.simulate_put(self.gumshoe_queue_path,
headers=headers, body=doc)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
location = self.srmock.headers_dict['Location']
self.assertEqual(self.gumshoe_queue_path, location)
# Fetch metadata
result = self.simulate_get(self.gumshoe_queue_path,
headers=headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(falcon.HTTP_200, self.srmock.status)
self.assertEqual(jsonutils.loads(doc), result_doc)
# Stats empty queue
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Delete
self.simulate_delete(self.gumshoe_queue_path, headers=headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Get non-existent stats
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
def test_name_restrictions(self):
self.simulate_put(self.queue_path + '/Nice-Boat_2',
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
self.simulate_put(self.queue_path + '/Nice-Bo@t',
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_put(self.queue_path + '/_' + 'niceboat' * 8,
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_project_id_restriction(self):
muvluv_queue_path = self.queue_path + '/Muv-Luv'
self.simulate_put(muvluv_queue_path,
headers={'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': 'JAM Project' * 24})
self.assertEqual(falcon.HTTP_400, self.srmock.status)
# no charset restrictions
self.simulate_put(muvluv_queue_path,
headers={'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': 'JAM Project'})
self.assertEqual(falcon.HTTP_201, self.srmock.status)
def test_non_ascii_name(self):
test_params = (('/queues/non-ascii-n\u0153me', 'utf-8'),
('/queues/non-ascii-n\xc4me', 'iso8859-1'))
for uri, enc in test_params:
uri = self.url_prefix + uri
self.simulate_put(uri, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
self.simulate_delete(uri, headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_no_metadata(self):
self.simulate_put(self.fizbat_queue_path,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
self.simulate_put(self.fizbat_queue_path, body='',
headers=self.headers)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
@ddt.data('{', '[]', '.', ' ')
def test_bad_metadata(self, document):
self.simulate_put(self.fizbat_queue_path,
headers=self.headers,
body=document)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_too_much_metadata(self):
self.simulate_put(self.fizbat_queue_path, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
doc = '{{"messages": {{"ttl": 600}}, "padding": "{pad}"}}'
max_size = self.transport_cfg.max_queue_metadata
padding_len = max_size - (len(doc) - 10) + 1
doc = doc.format(pad='x' * padding_len)
self.simulate_put(self.fizbat_queue_path,
headers=self.headers,
body=doc)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_way_too_much_metadata(self):
self.simulate_put(self.fizbat_queue_path, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
doc = '{{"messages": {{"ttl": 600}}, "padding": "{pad}"}}'
max_size = self.transport_cfg.max_queue_metadata
padding_len = max_size * 100
doc = doc.format(pad='x' * padding_len)
self.simulate_put(self.fizbat_queue_path,
headers=self.headers, body=doc)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_custom_metadata(self):
# Set
doc = '{{"messages": {{"ttl": 600}}, "padding": "{pad}"}}'
max_size = self.transport_cfg.max_queue_metadata
padding_len = max_size - (len(doc) - 2)
doc = doc.format(pad='x' * padding_len)
self.simulate_put(self.fizbat_queue_path,
headers=self.headers,
body=doc)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
# Get
result = self.simulate_get(self.fizbat_queue_path,
headers=self.headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(jsonutils.loads(doc), result_doc)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
def test_update_metadata(self):
self.skip("This should use patch instead")
xyz_queue_path = self.url_prefix + '/queues/xyz'
xyz_queue_path_metadata = xyz_queue_path
# Create
self.simulate_put(xyz_queue_path, headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
# Set meta
doc1 = '{"messages": {"ttl": 600}}'
self.simulate_put(xyz_queue_path_metadata,
headers=self.headers,
body=doc1)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Update
doc2 = '{"messages": {"ttl": 100}}'
self.simulate_put(xyz_queue_path_metadata,
headers=self.headers,
body=doc2)
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Get
result = self.simulate_get(xyz_queue_path_metadata,
headers=self.headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(jsonutils.loads(doc2), result_doc)
def test_list(self):
arbitrary_number = 644079696574693
project_id = str(arbitrary_number)
client_id = uuidutils.generate_uuid()
header = {
'X-Project-ID': project_id,
'Client-ID': client_id
}
# NOTE(kgriffs): It's important that this one sort after the one
# above. This is in order to prove that bug/1236605 is fixed, and
# stays fixed!
alt_project_id = str(arbitrary_number + 1)
# List empty
result = self.simulate_get(self.queue_path, headers=header)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
results = jsonutils.loads(result[0])
self.assertEqual([], results['queues'])
self.assertIn('links', results)
self.assertEqual(0, len(results['links']))
# Payload exceeded
self.simulate_get(self.queue_path, headers=header,
query_string='limit=21')
self.assertEqual(falcon.HTTP_400, self.srmock.status)
# Create some
def create_queue(name, project_id, body):
altheader = {'Client-ID': client_id}
if project_id is not None:
altheader['X-Project-ID'] = project_id
uri = self.queue_path + '/' + name
self.simulate_put(uri, headers=altheader, body=body)
create_queue('q1', project_id, '{"node": 31}')
create_queue('q2', project_id, '{"node": 32}')
create_queue('q3', project_id, '{"node": 33}')
create_queue('q3', alt_project_id, '{"alt": 1}')
# List (limit)
result = self.simulate_get(self.queue_path, headers=header,
query_string='limit=2')
result_doc = jsonutils.loads(result[0])
self.assertEqual(2, len(result_doc['queues']))
# List (no metadata, get all)
result = self.simulate_get(self.queue_path,
headers=header, query_string='limit=5')
result_doc = jsonutils.loads(result[0])
[target, params] = result_doc['links'][0]['href'].split('?')
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# Ensure we didn't pick up the queue from the alt project.
queues = result_doc['queues']
self.assertEqual(3, len(queues))
# List with metadata
result = self.simulate_get(self.queue_path, headers=header,
query_string='detailed=true')
self.assertEqual(falcon.HTTP_200, self.srmock.status)
result_doc = jsonutils.loads(result[0])
[target, params] = result_doc['links'][0]['href'].split('?')
queue = result_doc['queues'][0]
result = self.simulate_get(queue['href'], headers=header)
result_doc = jsonutils.loads(result[0])
self.assertEqual(queue['metadata'], result_doc)
self.assertEqual({'node': 31}, result_doc)
# List tail
self.simulate_get(target, headers=header, query_string=params)
self.assertEqual(falcon.HTTP_200, self.srmock.status)
# List manually-constructed tail
self.simulate_get(target, headers=header, query_string='marker=zzz')
self.assertEqual(falcon.HTTP_200, self.srmock.status)
def test_list_returns_503_on_nopoolfound_exception(self):
arbitrary_number = 644079696574693
project_id = str(arbitrary_number)
client_id = uuidutils.generate_uuid()
header = {
'X-Project-ID': project_id,
'Client-ID': client_id
}
queue_controller = self.boot.storage.queue_controller
with mock.patch.object(queue_controller, 'list') as mock_queue_list:
def queue_generator():
raise storage_errors.NoPoolFound()
# This generator tries to be like queue controller list generator
# in some ways.
def fake_generator():
yield queue_generator()
yield {}
mock_queue_list.return_value = fake_generator()
self.simulate_get(self.queue_path, headers=header)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
class TestQueueLifecycleFaultyDriver(base.V1_1BaseFaulty):
config_file = 'wsgi_faulty.conf'
def test_simple(self):
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
'X-Project-ID': '338730984abc_1'
}
gumshoe_queue_path = self.url_prefix + '/queues/gumshoe'
doc = '{"messages": {"ttl": 600}}'
self.simulate_put(gumshoe_queue_path,
headers=self.headers,
body=doc)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
location = ('Location', gumshoe_queue_path)
self.assertNotIn(location, self.srmock.headers)
result = self.simulate_get(gumshoe_queue_path,
headers=self.headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.assertNotEqual(result_doc, jsonutils.loads(doc))
self.simulate_get(gumshoe_queue_path + '/stats',
headers=self.headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_get(self.url_prefix + '/queues',
headers=self.headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)
self.simulate_delete(gumshoe_queue_path, headers=self.headers)
self.assertEqual(falcon.HTTP_503, self.srmock.status)

View File

@@ -1,137 +0,0 @@
# Copyright (c) 2013 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 falcon
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from zaqar.tests.unit.transport.wsgi import base
class TestValidation(base.V1_1Base):
config_file = 'wsgi_mongodb_validation.conf'
def setUp(self):
super(TestValidation, self).setUp()
self.project_id = '7e55e1a7e'
self.queue_path = self.url_prefix + '/queues/noein'
self.simulate_put(self.queue_path, self.project_id)
self.headers = {
'Client-ID': uuidutils.generate_uuid(),
}
def tearDown(self):
self.simulate_delete(self.queue_path, self.project_id)
super(TestValidation, self).tearDown()
def test_metadata_deserialization(self):
# Normal case
self.simulate_put(self.queue_path,
self.project_id,
body='{"timespace": "Shangri-la"}')
self.assertEqual(falcon.HTTP_204, self.srmock.status)
# Too long
max_queue_metadata = 64
doc_tmpl = '{{"Dragon Torc":"{0}"}}'
doc_tmpl_ws = '{{ "Dragon Torc" : "{0}" }}' # with whitespace
envelope_length = len(doc_tmpl.format(''))
for tmpl in doc_tmpl, doc_tmpl_ws:
gen = '0' * (max_queue_metadata - envelope_length + 1)
doc = tmpl.format(gen)
self.simulate_put(self.queue_path,
self.project_id,
body=doc)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_message_deserialization(self):
# Normal case
body = '{"messages": [{"body": "Dragon Knights", "ttl": 100}]}'
self.simulate_post(self.queue_path + '/messages',
self.project_id, body=body,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
# Both messages' size are too long
max_messages_post_size = 256
obj = {'a': 0, 'b': ''}
envelope_length = len(jsonutils.dumps(obj, separators=(',', ':')))
obj['b'] = 'x' * (max_messages_post_size - envelope_length + 1)
for long_body in ('a' * (max_messages_post_size - 2 + 1), obj):
doc = jsonutils.dumps([{'body': long_body, 'ttl': 100}])
self.simulate_post(self.queue_path + '/messages',
self.project_id,
body=doc,
headers=self.headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_request_without_client_id(self):
# No Client-ID in headers, it will raise 400 error.
empty_headers = {}
self.simulate_put(self.queue_path,
self.project_id,
headers=empty_headers)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_queue_metadata_putting(self):
# Test _default_message_ttl
# TTL normal case
queue_1 = self.url_prefix + '/queues/queue1'
self.simulate_put(queue_1,
self.project_id,
body='{"_default_message_ttl": 60}')
self.addCleanup(self.simulate_delete, queue_1, self.project_id,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
# TTL under min
self.simulate_put(queue_1,
self.project_id,
body='{"_default_message_ttl": 59}')
self.assertEqual(falcon.HTTP_400, self.srmock.status)
# TTL over max
self.simulate_put(queue_1,
self.project_id,
body='{"_default_message_ttl": 1209601}')
self.assertEqual(falcon.HTTP_400, self.srmock.status)
# Test _max_messages_post_size
# Size normal case
queue_2 = self.url_prefix + '/queues/queue2'
self.simulate_put(queue_2,
self.project_id,
body='{"_max_messages_post_size": 255}')
self.addCleanup(self.simulate_delete, queue_2, self.project_id,
headers=self.headers)
self.assertEqual(falcon.HTTP_201, self.srmock.status)
# Size over max
self.simulate_put(queue_2,
self.project_id,
body='{"_max_messages_post_size": 257}')
self.assertEqual(falcon.HTTP_400, self.srmock.status)