diff options
31 files changed, 628 insertions, 93 deletions
@@ -50,3 +50,6 @@ ChangeLog # Editors *~ .*.swp + +# reno build +releasenotes/build diff --git a/doc/source/cors.rst b/doc/source/cors.rst index 765a681..77bb6ed 100644 --- a/doc/source/cors.rst +++ b/doc/source/cors.rst @@ -55,18 +55,6 @@ something like this:: allow_headers=X-Custom-Header expose_headers=X-Custom-Header -If your software requires specific headers or methods for proper operation, you -may include these as latent properties. These will be evaluated in addition -to any found in configuration:: - - from oslo_middleware import cors - - app = cors.CORS(your_wsgi_application) - app.set_latent(allow_headers=['X-System-Header'], - expose_headers=['X-System-Header'], - allow_methods=['GET','PATCH']) - - Configuration for pastedeploy ----------------------------- @@ -74,7 +62,7 @@ If your application is using pastedeploy, the following configuration block will add CORS support.:: [filter:cors] - paste.filter_factory = oslo_middleware.cors:filter_factory + use = egg:oslo.middleware#cors allowed_origin=https://website.example.com:443,https://website2.example.com:443 max_age=3600 allow_methods=GET,POST,PUT,DELETE @@ -86,18 +74,12 @@ existing configuration from oslo_config in order to simplify the points of configuration, this may be done as follows.:: [filter:cors] - paste.filter_factory = oslo_middleware.cors:filter_factory + use = egg:oslo.middleware#cors oslo_config_project = oslo_project_name # Optional field, in case the program name is different from the project: oslo_config_program = oslo_project_name-api - # This method also permits setting latent properties, for any origins set - # in oslo config. - latent_allow_headers=X-Auth-Token - latent_expose_headers=X-Auth-Token - latent_methods=GET,PUT,POST - Configuration Options --------------------- diff --git a/doc/source/oslo_config.rst b/doc/source/oslo_config.rst index 6e772c6..e6134a5 100644 --- a/doc/source/oslo_config.rst +++ b/doc/source/oslo_config.rst @@ -23,7 +23,7 @@ Configuration with paste-deploy and the oslo.config The paste filter (in /etc/my_app/api-paste.ini) will looks like:: [filter:sizelimit] - paste.filter_factory = oslo_middleware.sizelimit:RequestBodySizeLimiter.factory + use = egg:oslo.middleware#sizelimit # In case of the application doesn't use the global oslo.config # object. The middleware must known the app name to load # the application configuration, by setting this: @@ -45,7 +45,7 @@ Configuration with pastedeploy only The paste filter (in /etc/my_app/api-paste.ini) will looks like:: [filter:sizelimit] - paste.filter_factory = oslo_middleware.sizelimit:RequestBodySizeLimiter.factory + use = egg:oslo.middleware#sizelimit max_request_body_size=1000 This will override any configuration done via oslo.config diff --git a/oslo_middleware/catch_errors.py b/oslo_middleware/catch_errors.py index 43d085f..782713b 100644 --- a/oslo_middleware/catch_errors.py +++ b/oslo_middleware/catch_errors.py @@ -37,6 +37,8 @@ class CatchErrors(base.ConfigurableMiddleware): try: response = req.get_response(self.application) except Exception: + if hasattr(req, 'environ') and 'HTTP_X_AUTH_TOKEN' in req.environ: + req.environ['HTTP_X_AUTH_TOKEN'] = '*****' LOG.exception(_LE('An error occurred during ' 'processing the request: %s'), req) response = webob.exc.HTTPInternalServerError() diff --git a/oslo_middleware/correlation_id.py b/oslo_middleware/correlation_id.py index 773dcba..0892ccd 100644 --- a/oslo_middleware/correlation_id.py +++ b/oslo_middleware/correlation_id.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid +from oslo_utils import uuidutils from oslo_middleware import base @@ -23,5 +23,5 @@ class CorrelationId(base.ConfigurableMiddleware): def process_request(self, req): correlation_id = (req.headers.get("X_CORRELATION_ID") or - str(uuid.uuid4())) + uuidutils.generate_uuid()) req.headers['X_CORRELATION_ID'] = correlation_id diff --git a/oslo_middleware/cors.py b/oslo_middleware/cors.py index 9da5d24..fb4f857 100644 --- a/oslo_middleware/cors.py +++ b/oslo_middleware/cors.py @@ -13,6 +13,7 @@ # limitations under the License. import copy +from debtcollector import moves import logging import debtcollector @@ -26,7 +27,6 @@ LOG = logging.getLogger(__name__) CORS_OPTS = [ cfg.ListOpt('allowed_origin', - default=None, help='Indicate whether this resource may be shared with the ' 'domain received in the requests "origin" header. ' 'Format: "<protocol>://<host>[:<port>]", no trailing ' @@ -245,6 +245,9 @@ class CORS(base.ConfigurableMiddleware): 'allow_headers': allow_headers } + @moves.moved_method('set_defaults', + message='CORS.set_latent has been deprecated in favor ' + 'of oslo_middleware.cors.set_defaults') def set_latent(self, allow_headers=None, allow_methods=None, expose_headers=None): '''Add a new latent property for this middleware. diff --git a/oslo_middleware/debug.py b/oslo_middleware/debug.py index fb2fc82..08eb0a0 100644 --- a/oslo_middleware/debug.py +++ b/oslo_middleware/debug.py @@ -19,7 +19,6 @@ from __future__ import print_function import sys -import six import webob.dec from oslo_middleware import base @@ -41,7 +40,7 @@ class Debug(base.ConfigurableMiddleware): resp = req.get_response(self.application) print(("*" * 40) + " RESPONSE HEADERS") - for (key, value) in six.iteritems(resp.headers): + for (key, value) in resp.headers.items(): print(key, "=", value) print() diff --git a/oslo_middleware/healthcheck/__init__.py b/oslo_middleware/healthcheck/__init__.py index ce2b03a..c85e1ac 100644 --- a/oslo_middleware/healthcheck/__init__.py +++ b/oslo_middleware/healthcheck/__init__.py @@ -21,6 +21,7 @@ import socket import sys import traceback +from debtcollector import removals import jinja2 from oslo_utils import reflection from oslo_utils import strutils @@ -37,6 +38,7 @@ except ImportError: greenlet = None from oslo_middleware import base +from oslo_middleware.healthcheck import opts def _find_objects(t): @@ -50,11 +52,10 @@ def _expand_template(contents, params): class Healthcheck(base.ConfigurableMiddleware): - """Healthcheck middleware used for monitoring. + """Healthcheck application used for monitoring. - If the path is ``/healthcheck``, it will respond 200 with "OK" as - the body. Or a 503 with the reason as the body if one of the backends - reports an application issue. + It will respond 200 with "OK" as the body. Or a 503 with the reason as the + body if one of the backends reports an application issue. This is useful for the following reasons: @@ -238,9 +239,8 @@ class Healthcheck(base.ConfigurableMiddleware): .. code-block:: ini - [filter:healthcheck] - paste.filter_factory = oslo_middleware:Healthcheck.factory - path = /healthcheck + [app:healthcheck] + use = egg:oslo.middleware:healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_disable @@ -252,26 +252,31 @@ class Healthcheck(base.ConfigurableMiddleware): .. code-block:: ini - [pipeline:public_api] - pipeline = healthcheck_public sizelimit [...] public_service + [composite:public_api] + use = egg:Paste#urlmap + / = public_api_pipeline + /healthcheck = healthcheck_public + + [composite:admin_api] + use = egg:Paste#urlmap + / = admin_api_pipeline + /healthcheck = healthcheck_admin - [pipeline:admin_api] - pipeline = healthcheck_admin sizelimit [...] admin_service + [pipeline:public_api_pipeline] + pipeline = sizelimit [...] public_service - [filter:healthcheck_public] - paste.filter_factory = oslo_middleware:Healthcheck.factory - path = /healthcheck_public + [pipeline:admin_api_pipeline] + pipeline = sizelimit [...] admin_service + + [app:healthcheck_public] + use = egg:oslo.middleware:healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_public_disable [filter:healthcheck_admin] - paste.filter_factory = oslo_middleware:Healthcheck.factory - path = /healthcheck_admin + use = egg:oslo.middleware:healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_admin_disable - - More details on available backends and their configuration can be found - on this page: :doc:`healthcheck_plugins`. """ NAMESPACE = "oslo.middleware.healthcheck" @@ -374,18 +379,16 @@ Reason </HTML> """ - def __init__(self, application, conf): - super(Healthcheck, self).__init__(application) - self._path = conf.get('path', '/healthcheck') - self._show_details = strutils.bool_from_string(conf.get('detailed')) - self._backend_names = [] - backends = conf.get('backends') - if backends: - self._backend_names = backends.split(',') + def __init__(self, *args, **kwargs): + super(Healthcheck, self).__init__(*args, **kwargs) + self.oslo_conf.register_opts(opts.HEALTHCHECK_OPTS, + group='healthcheck') + self._path = self._conf_get('path') + self._show_details = self._conf_get('detailed') self._backends = stevedore.NamedExtensionManager( - self.NAMESPACE, self._backend_names, + self.NAMESPACE, self._conf_get('backends'), name_order=True, invoke_on_load=True, - invoke_args=(conf,)) + invoke_args=(self.oslo_conf, self.conf)) self._accept_to_functor = collections.OrderedDict([ # Order here matters... ('text/plain', self._make_text_response), @@ -397,6 +400,34 @@ Reason # always return text/plain (because sending an error from this # middleware actually can cause issues). self._default_accept = 'text/plain' + self._ignore_path = False + + def _conf_get(self, key, group='healthcheck'): + return super(Healthcheck, self)._conf_get(key, group=group) + + @removals.remove( + message="The healthcheck middleware must now be configured as " + "an application, not as a filter") + @classmethod + def factory(cls, global_conf, **local_conf): + return super(Healthcheck, cls).factory(global_conf, **local_conf) + + @classmethod + def app_factory(cls, global_conf, **local_conf): + """Factory method for paste.deploy. + + :param global_conf: dict of options for all middlewares + (usually the [DEFAULT] section of the paste deploy + configuration file) + :param local_conf: options dedicated to this middleware + (usually the option defined in the middleware + section of the paste deploy configuration file) + """ + conf = global_conf.copy() if global_conf else {} + conf.update(local_conf) + o = cls(application=None, conf=conf) + o._ignore_path = True + return o @staticmethod def _get_threadstacks(): @@ -511,7 +542,7 @@ Reason @webob.dec.wsgify def process_request(self, req): - if req.path != self._path: + if not self._ignore_path and req.path != self._path: return None results = [ext.obj.healthcheck(req.server_port) for ext in self._backends] diff --git a/oslo_middleware/healthcheck/disable_by_file.py b/oslo_middleware/healthcheck/disable_by_file.py index 7fbb14b..c1eafa8 100644 --- a/oslo_middleware/healthcheck/disable_by_file.py +++ b/oslo_middleware/healthcheck/disable_by_file.py @@ -17,6 +17,7 @@ import logging import os from oslo_middleware._i18n import _LW +from oslo_middleware.healthcheck import opts from oslo_middleware.healthcheck import pluginbase LOG = logging.getLogger(__name__) @@ -39,24 +40,28 @@ class DisableByFilesPortsHealthcheck(pluginbase.HealthcheckBaseExtension): backends = disable_by_files_ports disable_by_file_paths = 5000:/var/run/keystone/healthcheck_disable, \ 35357:/var/run/keystone/admin_healthcheck_disable + # set to True to enable detailed output, False is the default + detailed = False """ - def __init__(self, conf): - super(DisableByFilesPortsHealthcheck, self).__init__(conf) + + def __init__(self, *args, **kwargs): + super(DisableByFilesPortsHealthcheck, self).__init__(*args, **kwargs) + self.oslo_conf.register_opts(opts.DISABLE_BY_FILES_OPTS, + group='healthcheck') self.status_files = {} - self.status_files.update( - self._iter_paths_ports(self.conf.get('disable_by_file_paths'))) + paths = self._conf_get('disable_by_file_paths') + self.status_files.update(self._iter_paths_ports(paths)) @staticmethod def _iter_paths_ports(paths): - if paths: - for port_path in paths.split(","): - port_path = port_path.strip() - if port_path: - # On windows, drive letters are followed by colons, - # which makes split() return 3 elements in this case - port, path = port_path.split(":", 1) - port = int(port) - yield (port, path) + for port_path in paths: + port_path = port_path.strip() + if port_path: + # On windows, drive letters are followed by colons, + # which makes split() return 3 elements in this case + port, path = port_path.split(":", 1) + port = int(port) + yield (port, path) def healthcheck(self, server_port): path = self.status_files.get(server_port) @@ -90,11 +95,18 @@ class DisableByFileHealthcheck(pluginbase.HealthcheckBaseExtension): path = /healthcheck backends = disable_by_file disable_by_file_path = /var/run/nova/healthcheck_disable + # set to True to enable detailed output, False is the default + detailed = False """ + def __init__(self, *args, **kwargs): + super(DisableByFileHealthcheck, self).__init__(*args, **kwargs) + self.oslo_conf.register_opts(opts.DISABLE_BY_FILE_OPTS, + group='healthcheck') + def healthcheck(self, server_port): - path = self.conf.get('disable_by_file_path') - if path is None: + path = self._conf_get('disable_by_file_path') + if not path: LOG.warning(_LW('DisableByFile healthcheck middleware enabled ' 'without disable_by_file_path set')) return pluginbase.HealthcheckResult( diff --git a/oslo_middleware/healthcheck/opts.py b/oslo_middleware/healthcheck/opts.py new file mode 100644 index 0000000..ff39e98 --- /dev/null +++ b/oslo_middleware/healthcheck/opts.py @@ -0,0 +1,47 @@ +# 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_config import cfg + + +HEALTHCHECK_OPTS = [ + cfg.StrOpt('path', + default='/healthcheck', + deprecated_for_removal=True, + help='The path to respond to healtcheck requests on.'), + cfg.BoolOpt('detailed', + default=False, + help='Show more detailed information as part of the response'), + cfg.ListOpt('backends', + default=[], + help='Additional backends that can perform health checks and ' + 'report that information back as part of a request.'), +] + + +DISABLE_BY_FILE_OPTS = [ + cfg.StrOpt('disable_by_file_path', + default=None, + help='Check the presence of a file to determine if an ' + 'application is running on a port. Used by ' + 'DisableByFileHealthcheck plugin.'), +] + + +DISABLE_BY_FILES_OPTS = [ + cfg.ListOpt('disable_by_file_paths', + default=[], + help='Check the presence of a file based on a port to ' + 'determine if an application is running on a port. ' + 'Expects a "port:path" list of strings. Used by ' + 'DisableByFilesPortsHealthcheck plugin.'), +] diff --git a/oslo_middleware/healthcheck/pluginbase.py b/oslo_middleware/healthcheck/pluginbase.py index e370967..eb8013d 100644 --- a/oslo_middleware/healthcheck/pluginbase.py +++ b/oslo_middleware/healthcheck/pluginbase.py @@ -29,7 +29,9 @@ class HealthcheckResult(object): @six.add_metaclass(abc.ABCMeta) class HealthcheckBaseExtension(object): - def __init__(self, conf): + + def __init__(self, oslo_conf, conf): + self.oslo_conf = oslo_conf self.conf = conf @abc.abstractmethod @@ -38,3 +40,10 @@ class HealthcheckBaseExtension(object): return: HealthcheckResult object """ + + def _conf_get(self, key, group='healthcheck'): + if key in self.conf: + # Validate value type + self.oslo_conf.set_override(key, self.conf[key], group=group, + enforce_type=True) + return getattr(getattr(self.oslo_conf, group), key) diff --git a/oslo_middleware/http_proxy_to_wsgi.py b/oslo_middleware/http_proxy_to_wsgi.py index 84bc32b..4d68bcf 100644 --- a/oslo_middleware/http_proxy_to_wsgi.py +++ b/oslo_middleware/http_proxy_to_wsgi.py @@ -71,6 +71,10 @@ class HTTPProxyToWSGI(base.ConfigurableMiddleware): if forwarded_host: req.environ['HTTP_HOST'] = forwarded_host + forwarded_for = proxy.get("for") + if forwarded_for: + req.environ['REMOTE_ADDR'] = forwarded_for + else: # World before RFC7239 forwarded_proto = req.environ.get("HTTP_X_FORWARDED_PROTO") @@ -81,6 +85,10 @@ class HTTPProxyToWSGI(base.ConfigurableMiddleware): if forwarded_host: req.environ['HTTP_HOST'] = forwarded_host + forwarded_for = req.environ.get("HTTP_X_FORWARDED_FOR") + if forwarded_for: + req.environ['REMOTE_ADDR'] = forwarded_for + v = req.environ.get("HTTP_X_FORWARDED_PREFIX") if v: req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME'] diff --git a/oslo_middleware/opts.py b/oslo_middleware/opts.py index e66e723..1584f9b 100644 --- a/oslo_middleware/opts.py +++ b/oslo_middleware/opts.py @@ -19,6 +19,7 @@ __all__ = [ 'list_opts_ssl', 'list_opts_cors', 'list_opts_http_proxy_to_wsgi', + 'list_opts_healthcheck', ] @@ -26,6 +27,7 @@ import copy import itertools from oslo_middleware import cors +from oslo_middleware.healthcheck import opts as healthcheck_opts from oslo_middleware import http_proxy_to_wsgi from oslo_middleware import sizelimit from oslo_middleware import ssl @@ -56,6 +58,7 @@ def list_opts(): list_opts_ssl(), list_opts_cors(), list_opts_http_proxy_to_wsgi(), + list_opts_healthcheck(), ) ) @@ -155,3 +158,31 @@ def list_opts_http_proxy_to_wsgi(): return [ ('oslo_middleware', copy.deepcopy(http_proxy_to_wsgi.OPTS)), ] + + +def list_opts_healthcheck(): + """Return a list of oslo.config options for healthcheck. + + The returned list includes all oslo.config options which may be registered + at runtime by the library. + + Each element of the list is a tuple. The first element is the name of the + group under which the list of elements in the second element will be + registered. A group name of None corresponds to the [DEFAULT] group in + config files. + + This function is also discoverable via the 'oslo.middleware' entry point + under the 'oslo.config.opts' namespace. + + The purpose of this is to allow tools like the Oslo sample config file + generator to discover the options exposed to users by this library. + + :returns: a list of (group_name, opts) tuples + """ + # standard opts and the most common plugin to turn up in sample config. + # can figure out a better way of exposing plugin opts later if required. + return [ + ('healthcheck', copy.deepcopy(healthcheck_opts.HEALTHCHECK_OPTS + + healthcheck_opts.DISABLE_BY_FILE_OPTS + + healthcheck_opts.DISABLE_BY_FILES_OPTS)) + ] diff --git a/oslo_middleware/ssl.py b/oslo_middleware/ssl.py index f90e954..aa96b11 100644 --- a/oslo_middleware/ssl.py +++ b/oslo_middleware/ssl.py @@ -24,10 +24,6 @@ OPTS = [ ] -removals.removed_module(__name__, - "oslo_middleware.http_proxy_to_wsgi") - - class SSLMiddleware(base.ConfigurableMiddleware): """SSL termination proxies middleware. @@ -37,6 +33,7 @@ class SSLMiddleware(base.ConfigurableMiddleware): """ def __init__(self, application, *args, **kwargs): + removals.removed_module(__name__, "oslo_middleware.http_proxy_to_wsgi") super(SSLMiddleware, self).__init__(application, *args, **kwargs) self.oslo_conf.register_opts(OPTS, group='oslo_middleware') diff --git a/oslo_middleware/tests/test_catch_errors.py b/oslo_middleware/tests/test_catch_errors.py index 920bbe2..66351e5 100644 --- a/oslo_middleware/tests/test_catch_errors.py +++ b/oslo_middleware/tests/test_catch_errors.py @@ -26,6 +26,7 @@ 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') + req.environ['HTTP_X_AUTH_TOKEN'] = 'hello=world' res = req.get_response(app) self.assertEqual(expected_code, res.status_int) @@ -45,3 +46,5 @@ class CatchErrorsTest(test_base.BaseTestCase): self._test_has_request_id(application, webob.exc.HTTPInternalServerError.code) self.assertEqual(1, log_exc.call_count) + req_log = log_exc.call_args[0][1] + self.assertIn('X-Auth-Token: *****', str(req_log)) diff --git a/oslo_middleware/tests/test_cors.py b/oslo_middleware/tests/test_cors.py index e8276f3..8efa2c8 100644 --- a/oslo_middleware/tests/test_cors.py +++ b/oslo_middleware/tests/test_cors.py @@ -361,7 +361,7 @@ class CORSRegularRequestTest(CORSTestBase): self.assertEqual(['http://valid.example.com'], gc.allowed_origin) self.assertEqual(False, gc.allow_credentials) self.assertEqual([], gc.expose_headers) - self.assertEqual(None, gc.max_age) + self.assertIsNone(gc.max_age) self.assertEqual(['GET'], gc.allow_methods) self.assertEqual([], gc.allow_headers) @@ -678,7 +678,7 @@ class CORSPreflightRequestTest(CORSTestBase): self.assertEqual(gc.allowed_origin, ['http://valid.example.com']) self.assertEqual(gc.allow_credentials, False) self.assertEqual(gc.expose_headers, []) - self.assertEqual(gc.max_age, None) + self.assertIsNone(gc.max_age) self.assertEqual(gc.allow_methods, ['GET']) self.assertEqual(gc.allow_headers, []) @@ -1195,7 +1195,7 @@ class CORSTestWildcard(CORSTestBase): self.assertEqual(['http://default.example.com'], gc.allowed_origin) self.assertEqual(True, gc.allow_credentials) self.assertEqual([], gc.expose_headers) - self.assertEqual(None, gc.max_age) + self.assertIsNone(gc.max_age) self.assertEqual(['GET', 'PUT', 'POST', 'DELETE', 'HEAD'], gc.allow_methods) self.assertEqual([], gc.allow_headers) diff --git a/oslo_middleware/tests/test_healthcheck.py b/oslo_middleware/tests/test_healthcheck.py index df73969..793a895 100644 --- a/oslo_middleware/tests/test_healthcheck.py +++ b/oslo_middleware/tests/test_healthcheck.py @@ -17,6 +17,7 @@ import threading import time import mock +from oslo_config import fixture as config from oslotest import base as test_base import requests import webob.dec @@ -50,6 +51,10 @@ class HealthcheckMainTests(test_base.BaseTestCase): class HealthcheckTests(test_base.BaseTestCase): + def setUp(self): + super(HealthcheckTests, self).setUp() + self.useFixture(config.Config()) + @staticmethod @webob.dec.wsgify def application(req): diff --git a/oslo_middleware/tests/test_http_proxy_to_wsgi.py b/oslo_middleware/tests/test_http_proxy_to_wsgi.py index 26baa77..1554ece 100644 --- a/oslo_middleware/tests/test_http_proxy_to_wsgi.py +++ b/oslo_middleware/tests/test_http_proxy_to_wsgi.py @@ -103,6 +103,29 @@ class TestHTTPProxyToWSGI(test_base.BaseTestCase): response = self.request.get_response(self.middleware) self.assertEqual(b"https://example.com:8043/bla", response.body) + def test_forwarded_for_headers(self): + @webob.dec.wsgify() + def fake_app(req): + return req.environ['REMOTE_ADDR'] + + self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGIMiddleware( + fake_app) + forwarded_for_addr = '1.2.3.4' + forwarded_addr = '8.8.8.8' + + # If both X-Forwarded-For and Fowarded headers are present, it should + # use the Forwarded header and ignore the X-Forwarded-For header. + self.request.headers['Forwarded'] = ( + "for=%s;proto=https;host=example.com:8043" % (forwarded_addr)) + self.request.headers['X-Forwarded-For'] = forwarded_for_addr + response = self.request.get_response(self.middleware) + self.assertEqual(forwarded_addr.encode(), response.body) + + # Now if only X-Forwarded-For header is present, it should be used. + del self.request.headers['Forwarded'] + response = self.request.get_response(self.middleware) + self.assertEqual(forwarded_for_addr.encode(), response.body) + class TestHTTPProxyToWSGIDisabled(test_base.BaseTestCase): diff --git a/oslo_middleware/version.py b/oslo_middleware/version.py new file mode 100644 index 0000000..f73224f --- /dev/null +++ b/oslo_middleware/version.py @@ -0,0 +1,18 @@ +# Copyright 2016 OpenStack Foundation +# +# 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 pbr.version + +version_info = pbr.version.VersionInfo('oslo_middleware') diff --git a/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml b/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml new file mode 100644 index 0000000..46a2da6 --- /dev/null +++ b/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml @@ -0,0 +1,3 @@ +--- +other: + - Switch to reno for managing release notes.
\ No newline at end of file diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/releasenotes/source/_static/.placeholder diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/releasenotes/source/_templates/.placeholder diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py new file mode 100644 index 0000000..a53fc2c --- /dev/null +++ b/releasenotes/source/conf.py @@ -0,0 +1,276 @@ +# -*- coding: utf-8 -*- +# 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. +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'oslosphinx', + 'reno.sphinxext', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'oslo.middleware Release Notes' +copyright = u'2016, oslo.middleware Developers' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +from oslo_middleware.version import version_info as oslo_middleware_version + +# The full version, including alpha/beta/rc tags. +release = oslo_middleware_version.version_string_with_vcs() +# The short X.Y version. +version = oslo_middleware_version.canonical_version_string() + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'oslo.middlewareReleaseNotesDoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'oslo.middlewareReleaseNotes.tex', + u'oslo.middleware Release Notes Documentation', + u'oslo.middleware Developers', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'oslo.middlewareReleaseNotes', + u'oslo.middleware Release Notes Documentation', + [u'oslo.middleware Developers'], 1) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'oslo.middlewareReleaseNotes', + u'oslo.middleware Release Notes Documentation', + u'oslo.middleware Developers', 'oslo.middlewareReleaseNotes', + 'The library includes components that can be injected into wsgi pipelines' + ' to intercept request/response flows.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# -- Options for Internationalization output ------------------------------ +locale_dirs = ['locale/'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst new file mode 100644 index 0000000..d19561d --- /dev/null +++ b/releasenotes/source/index.rst @@ -0,0 +1,8 @@ +============================= +oslo.middleware Release Notes +============================= + + .. toctree:: + :maxdepth: 1 + + unreleased diff --git a/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po new file mode 100644 index 0000000..7e82aef --- /dev/null +++ b/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po @@ -0,0 +1,27 @@ +# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: oslo.middleware Release Notes 3.20.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-11-02 02:56+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2016-10-22 06:02+0000\n" +"Last-Translator: Gérald LONLAS <g.lonlas@gmail.com>\n" +"Language-Team: French\n" +"Language: fr\n" +"X-Generator: Zanata 3.7.3\n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" + +msgid "Other Notes" +msgstr "Autres notes" + +msgid "Switch to reno for managing release notes." +msgstr "Commence à utiliser reno pour la gestion des notes de release" + +msgid "Unreleased Release Notes" +msgstr "Note de release pour les changements non déployées" + +msgid "oslo.middleware Release Notes" +msgstr "Note de release pour oslo.middleware" diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst new file mode 100644 index 0000000..5860a46 --- /dev/null +++ b/releasenotes/source/unreleased.rst @@ -0,0 +1,5 @@ +========================== + Unreleased Release Notes +========================== + +.. release-notes:: diff --git a/requirements.txt b/requirements.txt index 384efd3..11fc3e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,14 +2,14 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 # Apache-2.0 -Jinja2>=2.8 # BSD License (3 clause) -oslo.config>=3.14.0 # Apache-2.0 +pbr>=1.8 # Apache-2.0 +Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 # BSD License (3 clause) +oslo.config!=3.18.0,>=3.14.0 # Apache-2.0 oslo.context>=2.9.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.16.0 # Apache-2.0 +oslo.utils>=3.18.0 # Apache-2.0 six>=1.9.0 # MIT -stevedore>=1.16.0 # Apache-2.0 -WebOb>=1.2.3 # MIT +stevedore>=1.17.1 # Apache-2.0 +WebOb>=1.6.0 # MIT debtcollector>=1.2.0 # Apache-2.0 statsd>=3.2.1 # MIT @@ -5,7 +5,7 @@ description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://wiki.openstack.org/wiki/Oslo#oslo.middleware +home-page = http://docs.openstack.org/developer/oslo.middleware classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -16,7 +16,7 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 [files] packages = @@ -29,11 +29,15 @@ oslo.config.opts = oslo.middleware.sizelimit = oslo_middleware.opts:list_opts_sizelimit oslo.middleware.ssl = oslo_middleware.opts:list_opts_ssl oslo.middleware.http_proxy_to_wsgi = oslo_middleware.opts:list_opts_http_proxy_to_wsgi + oslo.middleware.healthcheck = oslo_middleware.opts:list_opts_healthcheck oslo.middleware.healthcheck = disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck +paste.app_factory = + healthcheck = oslo_middleware:Healthcheck.app_factory + paste.filter_factory = catch_errors = oslo_middleware:CatchErrors.factory correlation_id = oslo_middleware:CorrelationId.factory diff --git a/test-requirements.txt b/test-requirements.txt index 5f53419..26b5790 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,8 +5,9 @@ fixtures>=3.0.0 # Apache-2.0/BSD hacking<0.11,>=0.10.0 mock>=2.0 # BSD -oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD +sphinx!=1.3b1,<1.4,>=1.2.1 # BSD testtools>=1.4.0 # MIT -coverage>=3.6 # Apache-2.0 +coverage>=4.0 # Apache-2.0 +reno>=1.8.0 # Apache-2.0 diff --git a/tools/tox_install.sh b/tools/tox_install.sh new file mode 100755 index 0000000..e61b63a --- /dev/null +++ b/tools/tox_install.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Client constraint file contains this client version pin that is in conflict +# with installing the client from source. We should remove the version pin in +# the constraints file before applying it for from-source installation. + +CONSTRAINTS_FILE="$1" +shift 1 + +set -e + +# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get +# published to logs.openstack.org for easy debugging. +localfile="$VIRTUAL_ENV/log/upper-constraints.txt" + +if [[ "$CONSTRAINTS_FILE" != http* ]]; then + CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" +fi +# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep +curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" + +pip install -c"$localfile" openstack-requirements + +# This is the main purpose of the script: Allow local installation of +# the current repo. It is listed in constraints file and thus any +# install will be constrained and we need to unconstrain it. +edit-constraints "$localfile" -- "$CLIENT_NAME" + +pip install -c"$localfile" -U "$@" +exit $? @@ -1,8 +1,13 @@ [tox] -minversion = 1.6 -envlist = py34,py27,pypy,pep8 +minversion = 2.0 +envlist = py35,py27,pypy,pep8 [testenv] +setenv = + VIRTUAL_ENV={envdir} + BRANCH_NAME=master + CLIENT_NAME=oslo.middleware +install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} deps = -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' @@ -35,3 +40,6 @@ import_exceptions = oslo_middleware._i18n # of the requirements.txt files deps = pip_missing_reqs commands = pip-missing-reqs -d --ignore-module=oslo_middleware* --ignore-module=pkg_resources --ignore-file=oslo_middleware/tests/* oslo_middleware + +[testenv:releasenotes] +commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html |