From: gordon chung Date: Wed, 8 Oct 2014 21:51:47 +0000 (-0400) Subject: Migrate to oslo.middleware X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=064f763bb59b11077d332a08498ea8bf822407e7;p=openstack-build%2Fneutron-build.git Migrate to oslo.middleware Synced middleware module from incubator instead of removing it completely. This is needed for grenade and to keep backwards compatibility with existing installations with old api-paste.ini. 'log' module is updated as a dependency for middleware module. 'versionutils' are added as a new dependency for middleware module. Closes-Bug: #1371701 Change-Id: Ib1c3161ccc98642091134f2285fed7c90244e600 Co-Authored-By: Ihar Hrachyshka --- diff --git a/etc/api-paste.ini b/etc/api-paste.ini index bbcd41527..29f01e20b 100644 --- a/etc/api-paste.ini +++ b/etc/api-paste.ini @@ -9,10 +9,10 @@ noauth = request_id catch_errors extensions neutronapiapp_v2_0 keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0 [filter:request_id] -paste.filter_factory = neutron.openstack.common.middleware.request_id:RequestIdMiddleware.factory +paste.filter_factory = oslo.middleware:RequestId.factory [filter:catch_errors] -paste.filter_factory = neutron.openstack.common.middleware.catch_errors:CatchErrorsMiddleware.factory +paste.filter_factory = oslo.middleware:CatchErrors.factory [filter:keystonecontext] paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory diff --git a/neutron/auth.py b/neutron/auth.py index a91ab6d31..c0852f72a 100644 --- a/neutron/auth.py +++ b/neutron/auth.py @@ -13,12 +13,12 @@ # under the License. from oslo.config import cfg +from oslo.middleware import request_id import webob.dec import webob.exc from neutron import context from neutron.openstack.common import log as logging -from neutron.openstack.common.middleware import request_id from neutron import wsgi LOG = logging.getLogger(__name__) diff --git a/neutron/openstack/common/log.py b/neutron/openstack/common/log.py index a6f00b0c2..ddb1891ad 100644 --- a/neutron/openstack/common/log.py +++ b/neutron/openstack/common/log.py @@ -509,14 +509,9 @@ def _setup_logging_from_conf(project, version): log_root.addHandler(streamlog) if CONF.publish_errors: - try: - handler = importutils.import_object( - "neutron.openstack.common.log_handler.PublishErrorsHandler", - logging.ERROR) - except ImportError: - handler = importutils.import_object( - "oslo.messaging.notify.log_handler.PublishErrorsHandler", - logging.ERROR) + handler = importutils.import_object( + "oslo.messaging.notify.log_handler.PublishErrorsHandler", + logging.ERROR) log_root.addHandler(handler) datefmt = CONF.log_date_format diff --git a/neutron/openstack/common/middleware/base.py b/neutron/openstack/common/middleware/base.py deleted file mode 100644 index 464a1ccd7..000000000 --- a/neutron/openstack/common/middleware/base.py +++ /dev/null @@ -1,56 +0,0 @@ -# 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/neutron/openstack/common/middleware/catch_errors.py b/neutron/openstack/common/middleware/catch_errors.py index b692aeec8..d5dca1513 100644 --- a/neutron/openstack/common/middleware/catch_errors.py +++ b/neutron/openstack/common/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,14 @@ # License for the specific language governing permissions and limitations # under the License. -"""Middleware that provides high-level error handling. - -It catches all exceptions from subsequent applications in WSGI pipeline -to hide internal errors from API response. -""" - -import webob.dec -import webob.exc - -from neutron.openstack.common.gettextutils import _ # noqa -from neutron.openstack.common import log as logging -from neutron.openstack.common.middleware import base - +"""Compatibility shim for Kilo, while operators migrate to oslo.middleware.""" -LOG = logging.getLogger(__name__) +from oslo.middleware import catch_errors +from neutron.openstack.common import versionutils -class CatchErrorsMiddleware(base.Middleware): - @webob.dec.wsgify - def __call__(self, req): - try: - response = req.get_response(self.application) - except Exception: - LOG.exception(_('An error occurred during ' - 'processing the request: %s')) - response = webob.exc.HTTPInternalServerError() - return response +@versionutils.deprecated(as_of=versionutils.deprecated.KILO, + in_favor_of='oslo.middleware.CatchErrors') +class CatchErrorsMiddleware(catch_errors.CatchErrors): + pass diff --git a/neutron/openstack/common/middleware/correlation_id.py b/neutron/openstack/common/middleware/correlation_id.py deleted file mode 100644 index 80ee63f76..000000000 --- a/neutron/openstack/common/middleware/correlation_id.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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. - -"""Middleware that attaches a correlation id to WSGI request""" - -import uuid - -from neutron.openstack.common.middleware import base - - -class CorrelationIdMiddleware(base.Middleware): - - 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/neutron/openstack/common/middleware/debug.py b/neutron/openstack/common/middleware/debug.py deleted file mode 100644 index 5ab960554..000000000 --- a/neutron/openstack/common/middleware/debug.py +++ /dev/null @@ -1,60 +0,0 @@ -# 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 neutron.openstack.common.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/neutron/openstack/common/middleware/request_id.py b/neutron/openstack/common/middleware/request_id.py index 5c2620cce..7fea9c3e0 100644 --- a/neutron/openstack/common/middleware/request_id.py +++ b/neutron/openstack/common/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,29 +10,18 @@ # License for the specific language governing permissions and limitations # under the License. -"""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. -""" +"""Compatibility shim for Kilo, while operators migrate to oslo.middleware.""" -import webob.dec +from oslo.middleware import request_id -from neutron.openstack.common import context -from neutron.openstack.common.middleware import base +from neutron.openstack.common import versionutils ENV_REQUEST_ID = 'openstack.request_id' HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id' -class RequestIdMiddleware(base.Middleware): - - @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 +@versionutils.deprecated(as_of=versionutils.deprecated.KILO, + in_favor_of='oslo.middleware.RequestId') +class RequestIdMiddleware(request_id.RequestId): + pass diff --git a/neutron/openstack/common/middleware/sizelimit.py b/neutron/openstack/common/middleware/sizelimit.py deleted file mode 100644 index 56b320024..000000000 --- a/neutron/openstack/common/middleware/sizelimit.py +++ /dev/null @@ -1,81 +0,0 @@ -# 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 -import webob.dec -import webob.exc - -from neutron.openstack.common.gettextutils import _ -from neutron.openstack.common.middleware import base - - -#default request size is 112k -max_req_body_size = cfg.IntOpt('max_request_body_size', - deprecated_name='osapi_max_request_body_size', - default=114688, - help='the maximum body size ' - 'per each request(bytes)') - -CONF = cfg.CONF -CONF.register_opt(max_req_body_size) - - -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): - 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): - if req.content_length > CONF.max_request_body_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, - CONF.max_request_body_size) - req.body_file = limiter - return self.application diff --git a/neutron/openstack/common/versionutils.py b/neutron/openstack/common/versionutils.py new file mode 100644 index 000000000..7f1e02364 --- /dev/null +++ b/neutron/openstack/common/versionutils.py @@ -0,0 +1,203 @@ +# Copyright (c) 2013 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. + +""" +Helpers for comparing version strings. +""" + +import functools +import inspect + +import pkg_resources +import six + +from neutron.openstack.common._i18n import _ +from neutron.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +class deprecated(object): + """A decorator to mark callables as deprecated. + + This decorator logs a deprecation message when the callable it decorates is + used. The message will include the release where the callable was + deprecated, the release where it may be removed and possibly an optional + replacement. + + Examples: + + 1. Specifying the required deprecated release + + >>> @deprecated(as_of=deprecated.ICEHOUSE) + ... def a(): pass + + 2. Specifying a replacement: + + >>> @deprecated(as_of=deprecated.ICEHOUSE, in_favor_of='f()') + ... def b(): pass + + 3. Specifying the release where the functionality may be removed: + + >>> @deprecated(as_of=deprecated.ICEHOUSE, remove_in=+1) + ... def c(): pass + + 4. Specifying the deprecated functionality will not be removed: + >>> @deprecated(as_of=deprecated.ICEHOUSE, remove_in=0) + ... def d(): pass + + 5. Specifying a replacement, deprecated functionality will not be removed: + >>> @deprecated(as_of=deprecated.ICEHOUSE, in_favor_of='f()', remove_in=0) + ... def e(): pass + + """ + + # NOTE(morganfainberg): Bexar is used for unit test purposes, it is + # expected we maintain a gap between Bexar and Folsom in this list. + BEXAR = 'B' + FOLSOM = 'F' + GRIZZLY = 'G' + HAVANA = 'H' + ICEHOUSE = 'I' + JUNO = 'J' + KILO = 'K' + + _RELEASES = { + # NOTE(morganfainberg): Bexar is used for unit test purposes, it is + # expected we maintain a gap between Bexar and Folsom in this list. + 'B': 'Bexar', + 'F': 'Folsom', + 'G': 'Grizzly', + 'H': 'Havana', + 'I': 'Icehouse', + 'J': 'Juno', + 'K': 'Kilo', + } + + _deprecated_msg_with_alternative = _( + '%(what)s is deprecated as of %(as_of)s in favor of ' + '%(in_favor_of)s and may be removed in %(remove_in)s.') + + _deprecated_msg_no_alternative = _( + '%(what)s is deprecated as of %(as_of)s and may be ' + 'removed in %(remove_in)s. It will not be superseded.') + + _deprecated_msg_with_alternative_no_removal = _( + '%(what)s is deprecated as of %(as_of)s in favor of %(in_favor_of)s.') + + _deprecated_msg_with_no_alternative_no_removal = _( + '%(what)s is deprecated as of %(as_of)s. It will not be superseded.') + + def __init__(self, as_of, in_favor_of=None, remove_in=2, what=None): + """Initialize decorator + + :param as_of: the release deprecating the callable. Constants + are define in this class for convenience. + :param in_favor_of: the replacement for the callable (optional) + :param remove_in: an integer specifying how many releases to wait + before removing (default: 2) + :param what: name of the thing being deprecated (default: the + callable's name) + + """ + self.as_of = as_of + self.in_favor_of = in_favor_of + self.remove_in = remove_in + self.what = what + + def __call__(self, func_or_cls): + if not self.what: + self.what = func_or_cls.__name__ + '()' + msg, details = self._build_message() + + if inspect.isfunction(func_or_cls): + + @six.wraps(func_or_cls) + def wrapped(*args, **kwargs): + LOG.deprecated(msg, details) + return func_or_cls(*args, **kwargs) + return wrapped + elif inspect.isclass(func_or_cls): + orig_init = func_or_cls.__init__ + + # TODO(tsufiev): change `functools` module to `six` as + # soon as six 1.7.4 (with fix for passing `assigned` + # argument to underlying `functools.wraps`) is released + # and added to the neutron-incubator requrements + @functools.wraps(orig_init, assigned=('__name__', '__doc__')) + def new_init(self, *args, **kwargs): + LOG.deprecated(msg, details) + orig_init(self, *args, **kwargs) + func_or_cls.__init__ = new_init + return func_or_cls + else: + raise TypeError('deprecated can be used only with functions or ' + 'classes') + + def _get_safe_to_remove_release(self, release): + # TODO(dstanek): this method will have to be reimplemented once + # when we get to the X release because once we get to the Y + # release, what is Y+2? + new_release = chr(ord(release) + self.remove_in) + if new_release in self._RELEASES: + return self._RELEASES[new_release] + else: + return new_release + + def _build_message(self): + details = dict(what=self.what, + as_of=self._RELEASES[self.as_of], + remove_in=self._get_safe_to_remove_release(self.as_of)) + + if self.in_favor_of: + details['in_favor_of'] = self.in_favor_of + if self.remove_in > 0: + msg = self._deprecated_msg_with_alternative + else: + # There are no plans to remove this function, but it is + # now deprecated. + msg = self._deprecated_msg_with_alternative_no_removal + else: + if self.remove_in > 0: + msg = self._deprecated_msg_no_alternative + else: + # There are no plans to remove this function, but it is + # now deprecated. + msg = self._deprecated_msg_with_no_alternative_no_removal + return msg, details + + +def is_compatible(requested_version, current_version, same_major=True): + """Determine whether `requested_version` is satisfied by + `current_version`; in other words, `current_version` is >= + `requested_version`. + + :param requested_version: version to check for compatibility + :param current_version: version to check against + :param same_major: if True, the major version must be identical between + `requested_version` and `current_version`. This is used when a + major-version difference indicates incompatibility between the two + versions. Since this is the common-case in practice, the default is + True. + :returns: True if compatible, False if not + """ + requested_parts = pkg_resources.parse_version(requested_version) + current_parts = pkg_resources.parse_version(current_version) + + if same_major and (requested_parts[0] != current_parts[0]): + return False + + return current_parts >= requested_parts diff --git a/neutron/tests/unit/test_auth.py b/neutron/tests/unit/test_auth.py index 77f9d580c..8375c22a3 100644 --- a/neutron/tests/unit/test_auth.py +++ b/neutron/tests/unit/test_auth.py @@ -13,10 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo.middleware import request_id import webob from neutron import auth -from neutron.openstack.common.middleware import request_id from neutron.tests import base diff --git a/openstack-common.conf b/openstack-common.conf index 250aac48a..56f22ee7d 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -11,12 +11,7 @@ module=local module=lockutils module=log module=loopingcall -module=middleware.base -module=middleware.catch_errors -module=middleware.correlation_id -module=middleware.debug -module=middleware.request_id -module=middleware.sizelimit +module=middleware module=periodic_task module=policy module=processutils diff --git a/requirements.txt b/requirements.txt index c070f65cd..241a73c27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,6 +29,7 @@ oslo.config>=1.4.0 # Apache-2.0 oslo.db>=1.1.0 # Apache-2.0 oslo.i18n>=1.0.0 # Apache-2.0 oslo.messaging>=1.4.0 +oslo.middleware>=0.1.0 # Apache-2.0 oslo.rootwrap>=1.3.0 oslo.serialization>=1.0.0 # Apache-2.0 oslo.utils>=1.0.0 # Apache-2.0