diff --git a/doc/source/api.rst b/doc/source/api.rst index db58162..38cd96c 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -2,5 +2,5 @@ API ===== -.. automodule:: oslo.middleware +.. automodule:: oslo_middleware :members: diff --git a/oslo/middleware/__init__.py b/oslo/middleware/__init__.py index 5289943..1407ce1 100644 --- a/oslo/middleware/__init__.py +++ b/oslo/middleware/__init__.py @@ -10,14 +10,19 @@ # License for the specific language governing permissions and limitations # under the License. -__all__ = ['CatchErrors', - 'CorrelationId', - 'Debug', - 'RequestId', - 'RequestBodySizeLimiter'] +import warnings -from oslo.middleware.catch_errors import CatchErrors -from oslo.middleware.correlation_id import CorrelationId -from oslo.middleware.debug import Debug -from oslo.middleware.request_id import RequestId -from oslo.middleware.sizelimit import RequestBodySizeLimiter +from oslo_middleware import * + + +def deprecated(): + new_name = __name__.replace('.', '_') + warnings.warn( + ('The oslo namespace package is deprecated. Please use %s instead.' % + new_name), + DeprecationWarning, + stacklevel=3, + ) + + +deprecated() diff --git a/oslo/middleware/base.py b/oslo/middleware/base.py index 464a1cc..53e25e9 100644 --- a/oslo/middleware/base.py +++ b/oslo/middleware/base.py @@ -1,6 +1,3 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# # 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 @@ -13,44 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -"""Base class(es) for WSGI Middleware.""" - -import webob.dec - - -class Middleware(object): - """Base WSGI middleware wrapper. - - These classes require an application to be initialized that will be called - next. By default the middleware will simply call its wrapped app, or you - can override __call__ to customize its behavior. - """ - - @classmethod - def factory(cls, global_conf, **local_conf): - """Factory method for paste.deploy.""" - return cls - - def __init__(self, application): - self.application = application - - def process_request(self, req): - """Called on each request. - - If this returns None, the next application down the stack will be - executed. If it returns a response then that response will be returned - and execution will stop here. - """ - return None - - def process_response(self, response): - """Do whatever you'd like to the response.""" - return response - - @webob.dec.wsgify - def __call__(self, req): - response = self.process_request(req) - if response: - return response - response = req.get_response(self.application) - return self.process_response(response) +from oslo_middleware.base import * # noqa diff --git a/oslo/middleware/catch_errors.py b/oslo/middleware/catch_errors.py index ed57f8a..81e4c6c 100644 --- a/oslo/middleware/catch_errors.py +++ b/oslo/middleware/catch_errors.py @@ -1,6 +1,3 @@ -# Copyright (c) 2013 NEC Corporation -# All Rights Reserved. -# # 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 @@ -13,31 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -import logging - -import webob.dec -import webob.exc - -from oslo.middleware import base -from oslo.middleware.i18n import _LE - - -LOG = logging.getLogger(__name__) - - -class CatchErrors(base.Middleware): - """Middleware that provides high-level error handling. - - It catches all exceptions from subsequent applications in WSGI pipeline - to hide internal errors from API response. - """ - - @webob.dec.wsgify - def __call__(self, req): - try: - response = req.get_response(self.application) - except Exception: - LOG.exception(_LE('An error occurred during ' - 'processing the request: %s')) - response = webob.exc.HTTPInternalServerError() - return response +from oslo_middleware.catch_errors import * # noqa diff --git a/oslo/middleware/correlation_id.py b/oslo/middleware/correlation_id.py index 2cde134..fff548c 100644 --- a/oslo/middleware/correlation_id.py +++ b/oslo/middleware/correlation_id.py @@ -1,6 +1,3 @@ -# Copyright (c) 2013 Rackspace Hosting -# All Rights Reserved. -# # 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 @@ -13,15 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - -from oslo.middleware import base - - -class CorrelationId(base.Middleware): - "Middleware that attaches a correlation id to WSGI request" - - def process_request(self, req): - correlation_id = (req.headers.get("X_CORRELATION_ID") or - str(uuid.uuid4())) - req.headers['X_CORRELATION_ID'] = correlation_id +from oslo_middleware.correlation_id import * # noqa diff --git a/oslo/middleware/debug.py b/oslo/middleware/debug.py index 8244a02..2907289 100644 --- a/oslo/middleware/debug.py +++ b/oslo/middleware/debug.py @@ -1,6 +1,3 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# # 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 @@ -13,48 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -"""Debug middleware""" - -from __future__ import print_function - -import sys - -import six -import webob.dec - -from oslo.middleware import base - - -class Debug(base.Middleware): - """Helper class that returns debug information. - - Can be inserted into any WSGI application chain to get information about - the request and response. - """ - - @webob.dec.wsgify - def __call__(self, req): - print(("*" * 40) + " REQUEST ENVIRON") - for key, value in req.environ.items(): - print(key, "=", value) - print() - resp = req.get_response(self.application) - - print(("*" * 40) + " RESPONSE HEADERS") - for (key, value) in six.iteritems(resp.headers): - print(key, "=", value) - print() - - resp.app_iter = self.print_generator(resp.app_iter) - - return resp - - @staticmethod - def print_generator(app_iter): - """Prints the contents of a wrapper string iterator when iterated.""" - print(("*" * 40) + " BODY") - for part in app_iter: - sys.stdout.write(part) - sys.stdout.flush() - yield part - print() +from oslo_middleware.debug import * # noqa diff --git a/oslo/middleware/request_id.py b/oslo/middleware/request_id.py index a8663f8..81e3164 100644 --- a/oslo/middleware/request_id.py +++ b/oslo/middleware/request_id.py @@ -1,6 +1,3 @@ -# Copyright (c) 2013 NEC Corporation -# All Rights Reserved. -# # 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 @@ -13,28 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_context import context -import webob.dec - -from oslo.middleware import base - - -ENV_REQUEST_ID = 'openstack.request_id' -HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id' - - -class RequestId(base.Middleware): - """Middleware that ensures request ID. - - It ensures to assign request ID for each API request and set it to - request environment. The request ID is also added to API response. - """ - - @webob.dec.wsgify - def __call__(self, req): - req_id = context.generate_request_id() - req.environ[ENV_REQUEST_ID] = req_id - response = req.get_response(self.application) - if HTTP_RESP_HEADER_REQUEST_ID not in response.headers: - response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id) - return response +from oslo_middleware.request_id import * # noqa diff --git a/oslo/middleware/sizelimit.py b/oslo/middleware/sizelimit.py index f42c9e6..c04c1cd 100644 --- a/oslo/middleware/sizelimit.py +++ b/oslo/middleware/sizelimit.py @@ -1,5 +1,3 @@ -# Copyright (c) 2012 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 @@ -12,84 +10,4 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Request Body limiting middleware. - -""" - -from oslo.config import cfg -from oslo.config import cfgfilter -import webob.dec -import webob.exc - -from oslo.middleware import base -from oslo.middleware.i18n import _ - - -_oldopts = [cfg.DeprecatedOpt('osapi_max_request_body_size', - group='DEFAULT'), - cfg.DeprecatedOpt('max_request_body_size', - group='DEFAULT')] - -_opts = [ - # default request size is 112k - cfg.IntOpt('max_request_body_size', - default=114688, - help='The maximum body size for each ' - ' request, in bytes.', - deprecated_opts=_oldopts) -] - -CONF = cfgfilter.ConfigFilter(cfg.CONF) -CONF.register_opts(_opts, group='oslo_middleware') - - -class LimitingReader(object): - """Reader to limit the size of an incoming request.""" - def __init__(self, data, limit): - """Initiates LimitingReader object. - - :param data: Underlying data object - :param limit: maximum number of bytes the reader should allow - """ - self.data = data - self.limit = limit - self.bytes_read = 0 - - def __iter__(self): - for chunk in self.data: - self.bytes_read += len(chunk) - if self.bytes_read > self.limit: - msg = _("Request is too large.") - raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) - else: - yield chunk - - def read(self, i=None): - # NOTE(jamielennox): We can't simply provide the default to the read() - # call as the expected default differs between mod_wsgi and eventlet - if i is None: - result = self.data.read() - else: - result = self.data.read(i) - self.bytes_read += len(result) - if self.bytes_read > self.limit: - msg = _("Request is too large.") - raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) - return result - - -class RequestBodySizeLimiter(base.Middleware): - """Limit the size of incoming requests.""" - - @webob.dec.wsgify - def __call__(self, req): - max_size = CONF.oslo_middleware.max_request_body_size - if (req.content_length is not None and - req.content_length > max_size): - msg = _("Request is too large.") - raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) - if req.content_length is None and req.is_body_readable: - limiter = LimitingReader(req.body_file, max_size) - req.body_file = limiter - return self.application +from oslo_middleware.sizelimit import * # noqa diff --git a/oslo_middleware/__init__.py b/oslo_middleware/__init__.py new file mode 100644 index 0000000..a64dae0 --- /dev/null +++ b/oslo_middleware/__init__.py @@ -0,0 +1,23 @@ +# 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. + +__all__ = ['CatchErrors', + 'CorrelationId', + 'Debug', + 'RequestId', + 'RequestBodySizeLimiter'] + +from oslo_middleware.catch_errors import CatchErrors +from oslo_middleware.correlation_id import CorrelationId +from oslo_middleware.debug import Debug +from oslo_middleware.request_id import RequestId +from oslo_middleware.sizelimit import RequestBodySizeLimiter diff --git a/oslo_middleware/base.py b/oslo_middleware/base.py new file mode 100644 index 0000000..464a1cc --- /dev/null +++ b/oslo_middleware/base.py @@ -0,0 +1,56 @@ +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +"""Base class(es) for WSGI Middleware.""" + +import webob.dec + + +class Middleware(object): + """Base WSGI middleware wrapper. + + These classes require an application to be initialized that will be called + next. By default the middleware will simply call its wrapped app, or you + can override __call__ to customize its behavior. + """ + + @classmethod + def factory(cls, global_conf, **local_conf): + """Factory method for paste.deploy.""" + return cls + + def __init__(self, application): + self.application = application + + def process_request(self, req): + """Called on each request. + + If this returns None, the next application down the stack will be + executed. If it returns a response then that response will be returned + and execution will stop here. + """ + return None + + def process_response(self, response): + """Do whatever you'd like to the response.""" + return response + + @webob.dec.wsgify + def __call__(self, req): + response = self.process_request(req) + if response: + return response + response = req.get_response(self.application) + return self.process_response(response) diff --git a/oslo_middleware/catch_errors.py b/oslo_middleware/catch_errors.py new file mode 100644 index 0000000..89b4bd9 --- /dev/null +++ b/oslo_middleware/catch_errors.py @@ -0,0 +1,43 @@ +# Copyright (c) 2013 NEC Corporation +# All Rights Reserved. +# +# 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 webob.dec +import webob.exc + +from oslo_middleware import base +from oslo_middleware.i18n import _LE + + +LOG = logging.getLogger(__name__) + + +class CatchErrors(base.Middleware): + """Middleware that provides high-level error handling. + + It catches all exceptions from subsequent applications in WSGI pipeline + to hide internal errors from API response. + """ + + @webob.dec.wsgify + def __call__(self, req): + try: + response = req.get_response(self.application) + except Exception: + LOG.exception(_LE('An error occurred during ' + 'processing the request: %s')) + response = webob.exc.HTTPInternalServerError() + return response diff --git a/oslo_middleware/correlation_id.py b/oslo_middleware/correlation_id.py new file mode 100644 index 0000000..54d62ec --- /dev/null +++ b/oslo_middleware/correlation_id.py @@ -0,0 +1,27 @@ +# Copyright (c) 2013 Rackspace Hosting +# All Rights Reserved. +# +# 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 oslo_middleware import base + + +class CorrelationId(base.Middleware): + "Middleware that attaches a correlation id to WSGI request" + + def process_request(self, req): + correlation_id = (req.headers.get("X_CORRELATION_ID") or + str(uuid.uuid4())) + req.headers['X_CORRELATION_ID'] = correlation_id diff --git a/oslo_middleware/debug.py b/oslo_middleware/debug.py new file mode 100644 index 0000000..bdbf539 --- /dev/null +++ b/oslo_middleware/debug.py @@ -0,0 +1,60 @@ +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +"""Debug middleware""" + +from __future__ import print_function + +import sys + +import six +import webob.dec + +from oslo_middleware import base + + +class Debug(base.Middleware): + """Helper class that returns debug information. + + Can be inserted into any WSGI application chain to get information about + the request and response. + """ + + @webob.dec.wsgify + def __call__(self, req): + print(("*" * 40) + " REQUEST ENVIRON") + for key, value in req.environ.items(): + print(key, "=", value) + print() + resp = req.get_response(self.application) + + print(("*" * 40) + " RESPONSE HEADERS") + for (key, value) in six.iteritems(resp.headers): + print(key, "=", value) + print() + + resp.app_iter = self.print_generator(resp.app_iter) + + return resp + + @staticmethod + def print_generator(app_iter): + """Prints the contents of a wrapper string iterator when iterated.""" + print(("*" * 40) + " BODY") + for part in app_iter: + sys.stdout.write(part) + sys.stdout.flush() + yield part + print() diff --git a/oslo/middleware/i18n.py b/oslo_middleware/i18n.py similarity index 100% rename from oslo/middleware/i18n.py rename to oslo_middleware/i18n.py diff --git a/oslo/middleware/opts.py b/oslo_middleware/opts.py similarity index 97% rename from oslo/middleware/opts.py rename to oslo_middleware/opts.py index 3cc035f..d01fd05 100644 --- a/oslo/middleware/opts.py +++ b/oslo_middleware/opts.py @@ -20,7 +20,7 @@ __all__ = [ import copy -from oslo.middleware import sizelimit +from oslo_middleware import sizelimit def list_opts(): @@ -42,4 +42,4 @@ def list_opts(): :returns: a list of (group_name, opts) tuples """ - return [('oslo_middleware', copy.deepcopy(sizelimit._opts))] \ No newline at end of file + return [('oslo_middleware', copy.deepcopy(sizelimit._opts))] diff --git a/oslo_middleware/request_id.py b/oslo_middleware/request_id.py new file mode 100644 index 0000000..be9ae49 --- /dev/null +++ b/oslo_middleware/request_id.py @@ -0,0 +1,40 @@ +# Copyright (c) 2013 NEC Corporation +# All Rights Reserved. +# +# 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 oslo_context import context +import webob.dec + +from oslo_middleware import base + + +ENV_REQUEST_ID = 'openstack.request_id' +HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id' + + +class RequestId(base.Middleware): + """Middleware that ensures request ID. + + It ensures to assign request ID for each API request and set it to + request environment. The request ID is also added to API response. + """ + + @webob.dec.wsgify + def __call__(self, req): + req_id = context.generate_request_id() + req.environ[ENV_REQUEST_ID] = req_id + response = req.get_response(self.application) + if HTTP_RESP_HEADER_REQUEST_ID not in response.headers: + response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id) + return response diff --git a/oslo_middleware/sizelimit.py b/oslo_middleware/sizelimit.py new file mode 100644 index 0000000..42b0fd1 --- /dev/null +++ b/oslo_middleware/sizelimit.py @@ -0,0 +1,95 @@ +# Copyright (c) 2012 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. + +""" +Request Body limiting middleware. + +""" + +from oslo.config import cfg +from oslo.config import cfgfilter +import webob.dec +import webob.exc + +from oslo_middleware import base +from oslo_middleware.i18n import _ + + +_oldopts = [cfg.DeprecatedOpt('osapi_max_request_body_size', + group='DEFAULT'), + cfg.DeprecatedOpt('max_request_body_size', + group='DEFAULT')] + +_opts = [ + # default request size is 112k + cfg.IntOpt('max_request_body_size', + default=114688, + help='The maximum body size for each ' + ' request, in bytes.', + deprecated_opts=_oldopts) +] + +CONF = cfgfilter.ConfigFilter(cfg.CONF) +CONF.register_opts(_opts, group='oslo_middleware') + + +class LimitingReader(object): + """Reader to limit the size of an incoming request.""" + def __init__(self, data, limit): + """Initiates LimitingReader object. + + :param data: Underlying data object + :param limit: maximum number of bytes the reader should allow + """ + self.data = data + self.limit = limit + self.bytes_read = 0 + + def __iter__(self): + for chunk in self.data: + self.bytes_read += len(chunk) + if self.bytes_read > self.limit: + msg = _("Request is too large.") + raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) + else: + yield chunk + + def read(self, i=None): + # NOTE(jamielennox): We can't simply provide the default to the read() + # call as the expected default differs between mod_wsgi and eventlet + if i is None: + result = self.data.read() + else: + result = self.data.read(i) + self.bytes_read += len(result) + if self.bytes_read > self.limit: + msg = _("Request is too large.") + raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) + return result + + +class RequestBodySizeLimiter(base.Middleware): + """Limit the size of incoming requests.""" + + @webob.dec.wsgify + def __call__(self, req): + max_size = CONF.oslo_middleware.max_request_body_size + if (req.content_length is not None and + req.content_length > max_size): + msg = _("Request is too large.") + raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg) + if req.content_length is None and req.is_body_readable: + limiter = LimitingReader(req.body_file, max_size) + req.body_file = limiter + return self.application diff --git a/oslo_middleware/tests/__init__.py b/oslo_middleware/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oslo_middleware/tests/test_catch_errors.py b/oslo_middleware/tests/test_catch_errors.py new file mode 100644 index 0000000..920bbe2 --- /dev/null +++ b/oslo_middleware/tests/test_catch_errors.py @@ -0,0 +1,47 @@ +# Copyright (c) 2013 NEC Corporation +# All Rights Reserved. +# +# 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 +from oslotest import base as test_base +import webob.dec +import webob.exc + +from oslo_middleware import catch_errors + + +class CatchErrorsTest(test_base.BaseTestCase): + + def _test_has_request_id(self, application, expected_code=None): + app = catch_errors.CatchErrors(application) + req = webob.Request.blank('/test') + res = req.get_response(app) + self.assertEqual(expected_code, res.status_int) + + def test_success_response(self): + @webob.dec.wsgify + def application(req): + return 'Hello, World!!!' + + self._test_has_request_id(application, webob.exc.HTTPOk.code) + + def test_internal_server_error(self): + @webob.dec.wsgify + def application(req): + raise Exception() + + with mock.patch.object(catch_errors.LOG, 'exception') as log_exc: + self._test_has_request_id(application, + webob.exc.HTTPInternalServerError.code) + self.assertEqual(1, log_exc.call_count) diff --git a/oslo_middleware/tests/test_correlation_id.py b/oslo_middleware/tests/test_correlation_id.py new file mode 100644 index 0000000..6dde5d8 --- /dev/null +++ b/oslo_middleware/tests/test_correlation_id.py @@ -0,0 +1,53 @@ +# Copyright (c) 2013 Rackspace Hosting +# All Rights Reserved. +# +# 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 mock +from oslotest import base as test_base +from oslotest import moxstubout + +from oslo_middleware import correlation_id + + +class CorrelationIdTest(test_base.BaseTestCase): + + def setUp(self): + super(CorrelationIdTest, self).setUp() + self.stubs = self.useFixture(moxstubout.MoxStubout()).stubs + + def test_process_request(self): + app = mock.Mock() + req = mock.Mock() + req.headers = {} + + mock_uuid4 = mock.Mock() + mock_uuid4.return_value = "fake_uuid" + self.stubs.Set(uuid, 'uuid4', mock_uuid4) + + middleware = correlation_id.CorrelationId(app) + middleware(req) + + self.assertEqual(req.headers.get("X_CORRELATION_ID"), "fake_uuid") + + def test_process_request_should_not_regenerate_correlation_id(self): + app = mock.Mock() + req = mock.Mock() + req.headers = {"X_CORRELATION_ID": "correlation_id"} + + middleware = correlation_id.CorrelationId(app) + middleware(req) + + self.assertEqual(req.headers.get("X_CORRELATION_ID"), "correlation_id") diff --git a/oslo_middleware/tests/test_request_id.py b/oslo_middleware/tests/test_request_id.py new file mode 100644 index 0000000..09bdd32 --- /dev/null +++ b/oslo_middleware/tests/test_request_id.py @@ -0,0 +1,37 @@ +# Copyright (c) 2013 NEC Corporation +# All Rights Reserved. +# +# 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 oslotest import base as test_base +from testtools import matchers +import webob +import webob.dec + +from oslo_middleware import request_id + + +class RequestIdTest(test_base.BaseTestCase): + def test_generate_request_id(self): + @webob.dec.wsgify + def application(req): + return req.environ[request_id.ENV_REQUEST_ID] + + app = request_id.RequestId(application) + req = webob.Request.blank('/test') + res = req.get_response(app) + res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID) + self.assertThat(res_req_id, matchers.StartsWith(b'req-')) + # request-id in request environ is returned as response body + self.assertEqual(res_req_id, res.body) diff --git a/oslo_middleware/tests/test_sizelimit.py b/oslo_middleware/tests/test_sizelimit.py new file mode 100644 index 0000000..4611758 --- /dev/null +++ b/oslo_middleware/tests/test_sizelimit.py @@ -0,0 +1,108 @@ +# Copyright (c) 2012 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. + +from oslotest import base as test_base +import six +import webob + +from oslo.config import fixture as config +from oslo_middleware import sizelimit + + +class TestLimitingReader(test_base.BaseTestCase): + + def test_limiting_reader(self): + BYTES = 1024 + bytes_read = 0 + data = six.StringIO("*" * BYTES) + for chunk in sizelimit.LimitingReader(data, BYTES): + bytes_read += len(chunk) + + self.assertEqual(bytes_read, BYTES) + + bytes_read = 0 + data = six.StringIO("*" * BYTES) + reader = sizelimit.LimitingReader(data, BYTES) + byte = reader.read(1) + while len(byte) != 0: + bytes_read += 1 + byte = reader.read(1) + + self.assertEqual(bytes_read, BYTES) + + def test_read_default_value(self): + BYTES = 1024 + data_str = "*" * BYTES + data = six.StringIO(data_str) + reader = sizelimit.LimitingReader(data, BYTES) + res = reader.read() + self.assertEqual(data_str, res) + + def test_limiting_reader_fails(self): + BYTES = 1024 + + def _consume_all_iter(): + bytes_read = 0 + data = six.StringIO("*" * BYTES) + for chunk in sizelimit.LimitingReader(data, BYTES - 1): + bytes_read += len(chunk) + + self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, + _consume_all_iter) + + def _consume_all_read(): + bytes_read = 0 + data = six.StringIO("*" * BYTES) + reader = sizelimit.LimitingReader(data, BYTES - 1) + byte = reader.read(1) + while len(byte) != 0: + bytes_read += 1 + byte = reader.read(1) + + self.assertRaises(webob.exc.HTTPRequestEntityTooLarge, + _consume_all_read) + + +class TestRequestBodySizeLimiter(test_base.BaseTestCase): + + def setUp(self): + super(TestRequestBodySizeLimiter, self).setUp() + fixture = self.useFixture(config.Config(sizelimit.CONF)) + self.MAX_REQUEST_BODY_SIZE = \ + fixture.conf.oslo_middleware.max_request_body_size + + @webob.dec.wsgify() + def fake_app(req): + return webob.Response(req.body) + + self.middleware = sizelimit.RequestBodySizeLimiter(fake_app) + self.request = webob.Request.blank('/', method='POST') + + def test_content_length_acceptable(self): + self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE + self.request.body = b"0" * self.MAX_REQUEST_BODY_SIZE + response = self.request.get_response(self.middleware) + self.assertEqual(response.status_int, 200) + + def test_content_length_too_large(self): + self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE + 1 + self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1) + response = self.request.get_response(self.middleware) + self.assertEqual(response.status_int, 413) + + def test_request_too_large_no_content_length(self): + self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1) + self.request.headers['Content-Length'] = None + response = self.request.get_response(self.middleware) + self.assertEqual(response.status_int, 413) diff --git a/setup.cfg b/setup.cfg index 320dbcc..b44853b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,12 +22,13 @@ classifier = [files] packages = oslo + oslo_middleware namespace_packages = oslo [entry_points] oslo.config.opts = - oslo.middleware = oslo.middleware.opts:list_opts + oslo.middleware = oslo_middleware.opts:list_opts [build_sphinx] source-dir = doc/source diff --git a/tests/test_warning.py b/tests/test_warning.py new file mode 100644 index 0000000..8e7d96c --- /dev/null +++ b/tests/test_warning.py @@ -0,0 +1,61 @@ +# 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 imp +import os +import warnings + +import mock +from oslotest import base as test_base +import six + + +class DeprecationWarningTest(test_base.BaseTestCase): + + @mock.patch('warnings.warn') + def test_warning(self, mock_warn): + import oslo.middleware + imp.reload(oslo.middleware) + self.assertTrue(mock_warn.called) + args = mock_warn.call_args + self.assertIn('oslo_middleware', args[0][0]) + self.assertIn('deprecated', args[0][0]) + self.assertTrue(issubclass(args[0][1], DeprecationWarning)) + + def test_real_warning(self): + with warnings.catch_warnings(record=True) as warning_msgs: + warnings.resetwarnings() + warnings.simplefilter('always', DeprecationWarning) + import oslo.middleware + + # Use a separate function to get the stack level correct + # so we know the message points back to this file. This + # corresponds to an import or reload, which isn't working + # inside the test under Python 3.3. That may be due to a + # difference in the import implementation not triggering + # warnings properly when the module is reloaded, or + # because the warnings module is mostly implemented in C + # and something isn't cleanly resetting the global state + # used to track whether a warning needs to be + # emitted. Whatever the cause, we definitely see the + # warnings.warn() being invoked on a reload (see the test + # above) and warnings are reported on the console when we + # run the tests. A simpler test script run outside of + # testr does correctly report the warnings. + def foo(): + oslo.middleware.deprecated() + + foo() + self.assertEqual(1, len(warning_msgs)) + msg = warning_msgs[0] + self.assertIn('oslo_middleware', six.text_type(msg.message)) + self.assertEqual('test_warning.py', os.path.basename(msg.filename)) diff --git a/tox.ini b/tox.ini index 5895c1d..ea321a6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py26,py27,py33,py34,pypy,pep8 +envlist = py33,py34,py26,py27,pypy,pep8 # NOTE(dhellmann): We cannot set skipdist=True # for oslo libraries because of the namespace package. #skipsdist = True @@ -37,4 +37,4 @@ ignore = E123,E125,H305,H803,H904 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py [hacking] -import_exceptions = oslo.middleware.i18n +import_exceptions = oslo_middleware.i18n