diff --git a/marconi/tests/etc/wsgi_faulty.conf b/marconi/tests/etc/wsgi_faulty.conf new file mode 100644 index 000000000..f0235bf74 --- /dev/null +++ b/marconi/tests/etc/wsgi_faulty.conf @@ -0,0 +1,6 @@ +[drivers] +transport = marconi.transport.wsgi +storage = marconi.tests.util.faulty_storage + +[drivers:transport:wsgi] +port = 8888 diff --git a/marconi/tests/transport/wsgi/base.py b/marconi/tests/transport/wsgi/base.py new file mode 100644 index 000000000..e0a89d9e5 --- /dev/null +++ b/marconi/tests/transport/wsgi/base.py @@ -0,0 +1,37 @@ +# 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 falcon import testing + +import marconi +from marconi.tests import util + + +class TestBase(util.TestBase): + + config_filename = None + + def setUp(self): + super(TestBase, self).setUp() + + if self.config_filename is None: + self.skipTest("No config specified") + + conf_file = self.conf_path(self.config_filename) + boot = marconi.Bootstrap(conf_file) + + self.app = boot.transport.app + self.srmock = testing.StartResponseMock() diff --git a/marconi/tests/transport/wsgi/test_queue_lifecycle.py b/marconi/tests/transport/wsgi/test_queue_lifecycle.py index a511a1076..f63634a58 100644 --- a/marconi/tests/transport/wsgi/test_queue_lifecycle.py +++ b/marconi/tests/transport/wsgi/test_queue_lifecycle.py @@ -10,30 +10,23 @@ # 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 json +import os import falcon from falcon import testing -from testtools import matchers -import marconi -from marconi.tests import util +from marconi.tests.transport.wsgi import base from marconi import transport -class TestCreateQueue(util.TestBase): +class QueueLifecycleBaseTest(base.TestBase): - def setUp(self): - super(TestCreateQueue, self).setUp() - - conf_file = self.conf_path('wsgi_sqlite.conf') - boot = marconi.Bootstrap(conf_file) - - self.app = boot.transport.app - self.srmock = testing.StartResponseMock() + config_filename = None def test_simple(self): doc = '{"messages": {"ttl": 600}}' @@ -44,7 +37,7 @@ class TestCreateQueue(util.TestBase): self.assertEquals(self.srmock.status, falcon.HTTP_201) location = ('Location', '/v1/480924/queues/gumshoe') - self.assertThat(self.srmock.headers, matchers.Contains(location)) + self.assertIn(location, self.srmock.headers) env = testing.create_environ('/v1/480924/queues/gumshoe') result = self.app(env, self.srmock) @@ -116,3 +109,45 @@ class TestCreateQueue(util.TestBase): result = self.app(env, self.srmock) result_doc = json.loads(result[0]) self.assertEquals(result_doc, json.loads(doc2)) + + +class QueueLifecycleMongoDBTests(QueueLifecycleBaseTest): + + config_filename = 'wsgi_mongodb.conf' + + def setUp(self): + if not os.environ.get("MONGODB_TEST_LIVE"): + self.skipTest("No MongoDB instance running") + + super(QueueLifecycleMongoDBTests, self).setUp() + + +class QueueLifecycleSQLiteTests(QueueLifecycleBaseTest): + + config_filename = 'wsgi_sqlite.conf' + + +class QueueFaultyDriverTests(base.TestBase): + + config_filename = 'wsgi_faulty.conf' + + def test_simple(self): + doc = '{"messages": {"ttl": 600}}' + env = testing.create_environ('/v1/480924/queues/gumshoe', + method="PUT", body=doc) + + self.app(env, self.srmock) + self.assertEquals(self.srmock.status, falcon.HTTP_503) + + location = ('Location', '/v1/480924/queues/gumshoe') + self.assertNotIn(location, self.srmock.headers) + + env = testing.create_environ('/v1/480924/queues/gumshoe') + result = self.app(env, self.srmock) + self.assertEquals(self.srmock.status, falcon.HTTP_503) + self.assertNotEquals(result, [doc]) + + def test_bad_document(self): + env = testing.create_environ('/v1/480924/queues/bad-doc') + self.app(env, self.srmock) + self.assertEquals(self.srmock.status, falcon.HTTP_503) diff --git a/marconi/tests/util/faulty_storage.py b/marconi/tests/util/faulty_storage.py new file mode 100644 index 000000000..caa9b6fb7 --- /dev/null +++ b/marconi/tests/util/faulty_storage.py @@ -0,0 +1,69 @@ +# 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 marconi import storage + + +class Driver(storage.DriverBase): + @property + def queue_controller(self): + return QueueController(self) + + @property + def message_controller(self): + return MessageController(self) + + @property + def claim_controller(self): + return None + + +class QueueController(storage.QueueBase): + def __init__(self, driver): + pass + + def list(self, tenant=None): + raise NotImplementedError() + + def get(self, name, tenant=None): + raise NotImplementedError() + + def upsert(self, name, metadata, tenant=None): + raise NotImplementedError() + + def delete(self, name, tenant=None): + raise NotImplementedError() + + def stats(self, name, tenant=None): + raise NotImplementedError() + + def actions(self, name, tenant=None, marker=None, limit=10): + raise NotImplementedError() + + +class MessageController(storage.MessageBase): + def __init__(self, driver): + pass + + def get(self, queue, tenant=None, message_id=None, + marker=None, echo=False, client_uuid=None): + raise NotImplementedError() + + def post(self, queue, messages, tenant=None): + raise NotImplementedError() + + def delete(self, queue, message_id, tenant=None, claim=None): + raise NotImplementedError() diff --git a/marconi/transport/wsgi/queues.py b/marconi/transport/wsgi/queues.py index df9a58361..20f31c3a1 100644 --- a/marconi/transport/wsgi/queues.py +++ b/marconi/transport/wsgi/queues.py @@ -14,12 +14,16 @@ # limitations under the License. import json +import logging import falcon from marconi import transport +LOG = logging.getLogger(__name__) + + class QueuesResource(object): __slots__ = ('queue_ctrl') @@ -39,12 +43,33 @@ class QueuesResource(object): #TODO(kgriffs): check for malformed JSON, must be a hash at top level meta = json.load(req.stream) - #TODO(kgriffs): catch other kinds of exceptions - created = self.queue_ctrl.upsert(queue_name, meta, tenant=tenant_id) + try: + created = self.queue_ctrl.upsert(queue_name, meta, + tenant=tenant_id) + except Exception as ex: + LOG.error(ex) + title = _('Service temporarily unavailable') + msg = _('Please try again in a few seconds.') + raise falcon.HTTPServiceUnavailable(title, msg, 30) resp.status = falcon.HTTP_201 if created else falcon.HTTP_204 resp.location = req.path def on_get(self, req, resp, tenant_id, queue_name): - doc = self.queue_ctrl.get(queue_name, tenant=tenant_id) - resp.body = json.dumps(doc) + try: + doc = self.queue_ctrl.get(queue_name, tenant=tenant_id) + except Exception as ex: + LOG.error(ex) + title = _('Service temporarily unavailable') + msg = _('Please try again in a few seconds.') + raise falcon.HTTPServiceUnavailable(title, msg, 30) + + try: + resp.body = json.dumps(doc) + except TypeError as ex: + LOG.error(ex) + + #TODO(kgriffs): Improve these messages + title = _('Invalid queue metatada') + msg = _('The queue metadata could not be read.') + raise falcon.HTTPInternalServerError(title, msg)