diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index e739053..0000000 --- a/.coveragerc +++ /dev/null @@ -1,8 +0,0 @@ -[run] -branch = True -source = oslo_middleware -omit = oslo_middleware/tests/* - -[report] -ignore_errors = True -precision = 2 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ed88334..0000000 --- a/.gitignore +++ /dev/null @@ -1,52 +0,0 @@ -*.py[cod] - -# C extensions -*.so - -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg -lib -lib64 - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -cover -.tox -nosetests.xml -.testrepository - -# Translations -*.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# Complexity -output/*.html -output/*/index.html - -# Sphinx -doc/build - -# pbr generates these -AUTHORS -ChangeLog - -# Editors -*~ -.*.swp diff --git a/.gitreview b/.gitreview deleted file mode 100644 index 343cba3..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/oslo.middleware.git \ No newline at end of file diff --git a/.mailmap b/.mailmap deleted file mode 100644 index cc92f17..0000000 --- a/.mailmap +++ /dev/null @@ -1,3 +0,0 @@ -# Format is: -# -# \ No newline at end of file diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 6d83b3c..0000000 --- a/.testr.conf +++ /dev/null @@ -1,7 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ - OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ - OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 414d566..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,16 +0,0 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps in this page: - - http://docs.openstack.org/infra/manual/developers.html - -Once those steps have been completed, changes to OpenStack -should be submitted for review via the Gerrit tool, following -the workflow documented at: - - http://docs.openstack.org/infra/manual/developers.html#development-workflow - -Pull requests submitted through GitHub will be ignored. - -Bugs should be filed on Launchpad, not GitHub: - - https://bugs.launchpad.net/oslo.middleware diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index f67b666..0000000 --- a/HACKING.rst +++ /dev/null @@ -1,4 +0,0 @@ -oslo.middleware Style Commandments -================================== - -Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 67db858..0000000 --- a/LICENSE +++ /dev/null @@ -1,175 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. diff --git a/README.rst b/README.rst deleted file mode 100644 index 5bd6c32..0000000 --- a/README.rst +++ /dev/null @@ -1,21 +0,0 @@ -=================================== -oslo.middleware -=================================== - -.. image:: https://img.shields.io/pypi/v/oslo.middleware.svg - :target: https://pypi.python.org/pypi/oslo.middleware/ - :alt: Latest Version - -.. image:: https://img.shields.io/pypi/dm/oslo.middleware.svg - :target: https://pypi.python.org/pypi/oslo.middleware/ - :alt: Downloads - -Oslo middleware library includes components that can be injected into -wsgi pipelines to intercept request/response flows. The base class can be -enhanced with functionality like add/delete/modification of http headers -and support for limiting size/connection etc. - -* Free software: Apache license -* Documentation: http://docs.openstack.org/developer/oslo.middleware -* Source: http://git.openstack.org/cgit/openstack/oslo.middleware -* Bugs: http://bugs.launchpad.net/oslo.middleware diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..d9381b5 --- /dev/null +++ b/README.txt @@ -0,0 +1,13 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +Use instead the project deb-python-oslo.middleware at +http://git.openstack.org/cgit/openstack/deb-python-oslo.middleware . + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/babel.cfg b/babel.cfg deleted file mode 100644 index efceab8..0000000 --- a/babel.cfg +++ /dev/null @@ -1 +0,0 @@ -[python: **.py] diff --git a/doc/source/api.rst b/doc/source/api.rst deleted file mode 100644 index 50ad06d..0000000 --- a/doc/source/api.rst +++ /dev/null @@ -1,19 +0,0 @@ -===== - API -===== - -.. automodule:: oslo_middleware - :members: - -Configuration Options -===================== - -RequestBodySizeLimiter -~~~~~~~~~~~~~~~~~~~~~~ - -.. show-options:: oslo.middleware.sizelimit - -SSLMiddleware -~~~~~~~~~~~~~ - -.. show-options:: oslo.middleware.ssl diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100755 index a5301af..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- 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. - -import os -import sys - -sys.path.insert(0, os.path.abspath('../..')) -# -- General configuration ---------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - #'sphinx.ext.intersphinx', - 'oslosphinx', - 'oslo_config.sphinxext', - 'stevedore.sphinxext', -] - -# autodoc generation is a bit aggressive and a nuisance when doing heavy -# text edit cycles. -# execute "export SPHINX_DEBUG=1" in your terminal to disable - -# The suffix of source filenames. -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'oslo.middleware' -copyright = u'2014, OpenStack Foundation' - -# 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 - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -# html_theme_path = ["."] -# html_theme = '_theme' -# html_static_path = ['static'] - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - ('index', - '%s.tex' % project, - u'%s Documentation' % project, - u'OpenStack Foundation', 'manual'), -] - -# Example configuration for intersphinx: refer to the Python standard library. -#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst deleted file mode 100644 index 2ca75d1..0000000 --- a/doc/source/contributing.rst +++ /dev/null @@ -1,5 +0,0 @@ -============== - Contributing -============== - -.. include:: ../../CONTRIBUTING.rst diff --git a/doc/source/cors.rst b/doc/source/cors.rst deleted file mode 100644 index 765a681..0000000 --- a/doc/source/cors.rst +++ /dev/null @@ -1,112 +0,0 @@ -=============== -CORS Middleware -=============== - -This middleware provides a comprehensive, configurable implementation of the -CORS_ (Cross Origin Resource Sharing) specification as oslo-supported python -wsgi middleware. - -.. note:: - - While this middleware supports the use of the `*` wildcard origin in the - specification, this feature is not recommended for security reasons. It - is provided to simplify basic use of CORS, practically meaning "I don't - care how this is used." In an intranet setting, this could lead to leakage - of data beyond the intranet and therefore should be avoided. - -Quickstart ----------- -First, include the middleware in your application:: - - from oslo_middleware import cors - - app = cors.CORS(your_wsgi_application) - -Secondly, add as many allowed origins as you would like:: - - app.add_origin(allowed_origin='https://website.example.com:443', - allow_credentials=True, - max_age=3600, - allow_methods=['GET','PUT','POST','DELETE'], - allow_headers=['X-Custom-Header'], - expose_headers=['X-Custom-Header']) - - # ... add more origins here. - - -Configuration for oslo_config ------------------------------ - -A factory method has been provided to simplify configuration of your CORS -domain, using oslo_config:: - - from oslo_middleware import cors - from oslo_config import cfg - - app = cors.CORS(your_wsgi_application, cfg.CONF) - -In your application's config file, then include a configuration block -something like this:: - - [cors] - allowed_origin=https://website.example.com:443,https://website2.example.com:443 - max_age=3600 - allow_methods=GET,POST,PUT,DELETE - 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 ------------------------------ - -If your application is using pastedeploy, the following configuration block -will add CORS support.:: - - [filter:cors] - paste.filter_factory = oslo_middleware.cors:filter_factory - allowed_origin=https://website.example.com:443,https://website2.example.com:443 - max_age=3600 - allow_methods=GET,POST,PUT,DELETE - allow_headers=X-Custom-Header - expose_headers=X-Custom-Header - -If your application is using pastedeploy, but would also like to use the -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 - 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 ---------------------- - -.. show-options:: oslo.middleware.cors - -Module Documentation --------------------- - -.. automodule:: oslo_middleware.cors - :members: - -.. _CORS: http://www.w3.org/TR/cors/ diff --git a/doc/source/healthcheck_plugins.rst b/doc/source/healthcheck_plugins.rst deleted file mode 100644 index 3ea93c5..0000000 --- a/doc/source/healthcheck_plugins.rst +++ /dev/null @@ -1,16 +0,0 @@ -================================ - Healthcheck middleware plugins -================================ - -.. automodule:: oslo_middleware.healthcheck - :members: - -.. automodule:: oslo_middleware.healthcheck.disable_by_file - :members: - - -Available Plugins ------------------- - -.. list-plugins:: oslo.middleware.healthcheck - :detailed: diff --git a/doc/source/history.rst b/doc/source/history.rst deleted file mode 100644 index 69ed4fe..0000000 --- a/doc/source/history.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../ChangeLog diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index f53e5bd..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. include:: ../../README.rst - -Contents -======== - -.. toctree:: - :maxdepth: 2 - - installation - api - healthcheck_plugins - cors - oslo_config - contributing - -Release Notes -============= - -.. toctree:: - :maxdepth: 1 - - history diff --git a/doc/source/installation.rst b/doc/source/installation.rst deleted file mode 100644 index 4fd1d47..0000000 --- a/doc/source/installation.rst +++ /dev/null @@ -1,12 +0,0 @@ -============ -Installation -============ - -At the command line:: - - $ pip install oslo.middleware - -Or, if you have virtualenvwrapper installed:: - - $ mkvirtualenv oslo.middleware - $ pip install oslo.middleware \ No newline at end of file diff --git a/doc/source/oslo_config.rst b/doc/source/oslo_config.rst deleted file mode 100644 index 6e772c6..0000000 --- a/doc/source/oslo_config.rst +++ /dev/null @@ -1,57 +0,0 @@ -============================= -Middlewares and configuration -============================= - -Middlewares can be configured in multiple fashion depending of the -application needs. Here is some use-cases: - -Configuration from the application ----------------------------------- - -The application code will looks like:: - - from oslo_middleware import sizelimit - from oslo_config import cfg - - conf = cfg.ConfigOpts() - app = sizelimit.RequestBodySizeLimiter(your_wsgi_application, conf) - - -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 - # 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: - # oslo_config_project = my_app - - # In some cases, you may need to specify the program name for the project - # as well. - # oslo_config_program = my_app-api - -The oslo.config file of the application (eg: /etc/my_app/my_app.conf) will looks like:: - - [oslo_middleware] - max_request_body_size=1000 - - -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 - max_request_body_size=1000 - -This will override any configuration done via oslo.config - - -.. note:: - - healtcheck middleware does not yet use oslo.config, see :doc:`healthcheck_plugins` - diff --git a/oslo/__init__.py b/oslo/__init__.py deleted file mode 100644 index dc130d6..0000000 --- a/oslo/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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__('pkg_resources').declare_namespace(__name__) diff --git a/oslo/middleware/__init__.py b/oslo/middleware/__init__.py deleted file mode 100644 index 844bf9d..0000000 --- a/oslo/middleware/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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 sys -import warnings - -def deprecated(): - new_name = __name__.replace('.', '_') - warnings.warn( - ('The oslo namespace package is deprecated. Please use %s instead.' % - new_name), - DeprecationWarning, - stacklevel=3, - ) - - -# NOTE(dims): We cannot remove the deprecation or redirects below -# until Liberty-EOL -deprecated() - -from oslo_middleware import base -from oslo_middleware import catch_errors -from oslo_middleware import correlation_id -from oslo_middleware import debug -from oslo_middleware import request_id -from oslo_middleware import sizelimit - -sys.modules['oslo.middleware.base'] = base -sys.modules['oslo.middleware.catch_errors'] = catch_errors -sys.modules['oslo.middleware.correlation_id'] = correlation_id -sys.modules['oslo.middleware.debug'] = debug -sys.modules['oslo.middleware.request_id'] = request_id -sys.modules['oslo.middleware.sizelimit'] = sizelimit - -from oslo_middleware.catch_errors import CatchErrors -from oslo_middleware.correlation_id import CorrelationId -from oslo_middleware.cors import CORS -from oslo_middleware.debug import Debug -from oslo_middleware.healthcheck import Healthcheck -from oslo_middleware.http_proxy_to_wsgi import HTTPProxyToWSGI -from oslo_middleware.request_id import RequestId -from oslo_middleware.sizelimit import RequestBodySizeLimiter -from oslo_middleware.ssl import SSLMiddleware \ No newline at end of file diff --git a/oslo_middleware/__init__.py b/oslo_middleware/__init__.py deleted file mode 100644 index ea1c12d..0000000 --- a/oslo_middleware/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# 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', - 'CORS', - 'Debug', - 'Healthcheck', - 'HTTPProxyToWSGI', - 'RequestId', - 'RequestBodySizeLimiter', - 'SSLMiddleware'] - -from oslo_middleware.catch_errors import CatchErrors -from oslo_middleware.correlation_id import CorrelationId -from oslo_middleware.cors import CORS -from oslo_middleware.debug import Debug -from oslo_middleware.healthcheck import Healthcheck -from oslo_middleware.http_proxy_to_wsgi import HTTPProxyToWSGI -from oslo_middleware.request_id import RequestId -from oslo_middleware.sizelimit import RequestBodySizeLimiter -from oslo_middleware.ssl import SSLMiddleware diff --git a/oslo_middleware/_i18n.py b/oslo_middleware/_i18n.py deleted file mode 100644 index f9bbda1..0000000 --- a/oslo_middleware/_i18n.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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. - -"""oslo.i18n integration module. - -See http://docs.openstack.org/developer/oslo.i18n/usage.html - -""" - -import oslo_i18n - - -_translators = oslo_i18n.TranslatorFactory(domain='oslo_middleware') - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical diff --git a/oslo_middleware/base.py b/oslo_middleware/base.py deleted file mode 100644 index 27689a1..0000000 --- a/oslo_middleware/base.py +++ /dev/null @@ -1,144 +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.""" - -from inspect import getargspec -import webob.dec -import webob.request -import webob.response - -from oslo_config import cfg - - -class NoContentTypeResponse(webob.response.Response): - - default_content_type = None # prevents webob assigning content type - - -class NoContentTypeRequest(webob.request.Request): - - ResponseClass = NoContentTypeResponse - - -class ConfigurableMiddleware(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. - - :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) - - def middleware_filter(app): - return cls(app, conf) - - return middleware_filter - - def __init__(self, application, conf=None): - """Base middleware constructor - - :param conf: a dict of options or a cfg.ConfigOpts object - """ - self.application = application - - # NOTE(sileht): If the configuration come from oslo.config - # just use it. - if isinstance(conf, cfg.ConfigOpts): - self.conf = {} - self.oslo_conf = conf - else: - self.conf = conf or {} - if "oslo_config_project" in self.conf: - if 'oslo_config_file' in self.conf: - default_config_files = [self.conf['oslo_config_file']] - else: - default_config_files = None - - if 'oslo_config_program' in self.conf: - program = self.conf['oslo_config_program'] - else: - program = None - - self.oslo_conf = cfg.ConfigOpts() - self.oslo_conf([], - project=self.conf['oslo_config_project'], - prog=program, - default_config_files=default_config_files, - validate_default_values=True) - - else: - # Fallback to global object - self.oslo_conf = cfg.CONF - - def _conf_get(self, key, group="oslo_middleware"): - 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) - - @staticmethod - def process_request(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 - - @staticmethod - def process_response(response, request=None): - """Do whatever you'd like to the response.""" - return response - - @webob.dec.wsgify(RequestClass=NoContentTypeRequest) - def __call__(self, req): - response = self.process_request(req) - if response: - return response - response = req.get_response(self.application) - - (args, varargs, varkw, defaults) = getargspec(self.process_response) - if 'request' in args: - return self.process_response(response, request=req) - return self.process_response(response) - - -class Middleware(ConfigurableMiddleware): - """Legacy base WSGI middleware wrapper. - - Legacy interface that doesn't pass configuration options - to the middleware when it's loaded via paste.deploy. - """ - - @classmethod - def factory(cls, global_conf, **local_conf): - """Factory method for paste.deploy.""" - return cls diff --git a/oslo_middleware/catch_errors.py b/oslo_middleware/catch_errors.py deleted file mode 100644 index 43d085f..0000000 --- a/oslo_middleware/catch_errors.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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._i18n import _LE -from oslo_middleware import base - - -LOG = logging.getLogger(__name__) - - -class CatchErrors(base.ConfigurableMiddleware): - """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'), req) - response = webob.exc.HTTPInternalServerError() - return response diff --git a/oslo_middleware/correlation_id.py b/oslo_middleware/correlation_id.py deleted file mode 100644 index 773dcba..0000000 --- a/oslo_middleware/correlation_id.py +++ /dev/null @@ -1,27 +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. - -import uuid - -from oslo_middleware import base - - -class CorrelationId(base.ConfigurableMiddleware): - "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/cors.py b/oslo_middleware/cors.py deleted file mode 100644 index 9da5d24..0000000 --- a/oslo_middleware/cors.py +++ /dev/null @@ -1,452 +0,0 @@ -# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. -# -# 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 copy -import logging - -import debtcollector -from oslo_config import cfg -from oslo_middleware import base -import six -import webob.exc - - -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: "://[:]", no trailing ' - 'slash. Example: https://horizon.example.com'), - cfg.BoolOpt('allow_credentials', - default=True, - help='Indicate that the actual request can include user ' - 'credentials'), - cfg.ListOpt('expose_headers', - default=[], - help='Indicate which headers are safe to expose to the API. ' - 'Defaults to HTTP Simple Headers.'), - cfg.IntOpt('max_age', - default=3600, - help='Maximum cache age of CORS preflight requests.'), - cfg.ListOpt('allow_methods', - default=['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', - 'TRACE', 'PATCH'], # RFC 2616, RFC 5789 - help='Indicate which methods can be used during the actual ' - 'request.'), - cfg.ListOpt('allow_headers', - default=[], - help='Indicate which header field names may be used during ' - 'the actual request.') -] - - -def set_defaults(**kwargs): - """Override the default values for configuration options. - - This method permits a project to override the default CORS option values. - For example, it may wish to offer a set of sane default headers which - allow it to function with only minimal additional configuration. - - :param allow_credentials: Whether to permit credentials. - :type allow_credentials: bool - :param expose_headers: A list of headers to expose. - :type expose_headers: List of Strings - :param max_age: Maximum cache duration in seconds. - :type max_age: Int - :param allow_methods: List of HTTP methods to permit. - :type allow_methods: List of Strings - :param allow_headers: List of HTTP headers to permit from the client. - :type allow_headers: List of Strings - """ - # Since 'None' is a valid config override, we have to use kwargs. Else - # there's no good way for a user to override only one option, because all - # the others would be overridden to 'None'. - - valid_params = set(k.name for k in CORS_OPTS - if k.name != 'allowed_origin') - passed_params = set(k for k in kwargs) - - wrong_params = passed_params - valid_params - if wrong_params: - raise AttributeError('Parameter(s) [%s] invalid, please only use [%s]' - % (wrong_params, valid_params)) - - # Set global defaults. - cfg.set_defaults(CORS_OPTS, **kwargs) - - -class InvalidOriginError(Exception): - """Exception raised when Origin is invalid.""" - - def __init__(self, origin): - self.origin = origin - super(InvalidOriginError, self).__init__( - 'CORS request from origin \'%s\' not permitted.' % origin) - - -class CORS(base.ConfigurableMiddleware): - """CORS Middleware. - - This middleware allows a WSGI app to serve CORS headers for multiple - configured domains. - - For more information, see http://www.w3.org/TR/cors/ - """ - - simple_headers = [ - 'Accept', - 'Accept-Language', - 'Content-Type', - 'Cache-Control', - 'Content-Language', - 'Expires', - 'Last-Modified', - 'Pragma' - ] - - def __init__(self, application, *args, **kwargs): - super(CORS, self).__init__(application, *args, **kwargs) - # Begin constructing our configuration hash. - self.allowed_origins = {} - self._init_conf() - - def sanitize(csv_list): - try: - return [str.strip(x) for x in csv_list.split(',')] - except Exception: - return None - - self.set_latent( - allow_headers=sanitize(self.conf.get('latent_allow_headers')), - expose_headers=sanitize(self.conf.get('latent_expose_headers')), - allow_methods=sanitize(self.conf.get('latent_allow_methods')) - ) - - @classmethod - def factory(cls, global_conf, **local_conf): - """factory method for paste.deploy - - allowed_origin: Protocol, host, and port for the allowed origin. - allow_credentials: Whether to permit credentials. - expose_headers: A list of headers to expose. - max_age: Maximum cache duration. - allow_methods: List of HTTP methods to permit. - allow_headers: List of HTTP headers to permit from the client. - """ - if ('allowed_origin' not in local_conf - and 'oslo_config_project' not in local_conf): - raise TypeError("allowed_origin or oslo_config_project " - "is required") - return super(CORS, cls).factory(global_conf, **local_conf) - - def _init_conf(self): - '''Initialize this middleware from an oslo.config instance.''' - - # Set up a location for our latent configuration options - self._latent_configuration = { - 'allow_headers': [], - 'expose_headers': [], - 'methods': [] - } - - # First, check the configuration and register global options. - self.oslo_conf.register_opts(CORS_OPTS, 'cors') - - allowed_origin = self._conf_get('allowed_origin', 'cors') - allow_credentials = self._conf_get('allow_credentials', 'cors') - expose_headers = self._conf_get('expose_headers', 'cors') - max_age = self._conf_get('max_age', 'cors') - allow_methods = self._conf_get('allow_methods', 'cors') - allow_headers = self._conf_get('allow_headers', 'cors') - - # Clone our original CORS_OPTS, and set the defaults to whatever is - # set in the global conf instance. This is done explicitly (instead - # of **kwargs), since we don't accidentally want to catch - # allowed_origin. - subgroup_opts = copy.deepcopy(CORS_OPTS) - cfg.set_defaults(subgroup_opts, - allow_credentials=allow_credentials, - expose_headers=expose_headers, - max_age=max_age, - allow_methods=allow_methods, - allow_headers=allow_headers) - - # If the default configuration contains an allowed_origin, don't - # forget to register that. - self.add_origin(allowed_origin=allowed_origin, - allow_credentials=allow_credentials, - expose_headers=expose_headers, - max_age=max_age, - allow_methods=allow_methods, - allow_headers=allow_headers) - - # Iterate through all the loaded config sections, looking for ones - # prefixed with 'cors.' - for section in self.oslo_conf.list_all_sections(): - if section.startswith('cors.'): - debtcollector.deprecate('Multiple configuration blocks are ' - 'deprecated and will be removed in ' - 'future versions. Please consolidate ' - 'your configuration in the [cors] ' - 'configuration block.') - # Register with the preconstructed defaults - self.oslo_conf.register_opts(subgroup_opts, section) - self.add_origin(**self.oslo_conf[section]) - - def add_origin(self, allowed_origin, allow_credentials=True, - expose_headers=None, max_age=None, allow_methods=None, - allow_headers=None): - '''Add another origin to this filter. - - :param allowed_origin: Protocol, host, and port for the allowed origin. - :param allow_credentials: Whether to permit credentials. - :param expose_headers: A list of headers to expose. - :param max_age: Maximum cache duration. - :param allow_methods: List of HTTP methods to permit. - :param allow_headers: List of HTTP headers to permit from the client. - :return: - ''' - - # NOTE(dims): Support older code that still passes in - # a string for allowed_origin instead of a list - if isinstance(allowed_origin, six.string_types): - # TODO(krotscheck): https://review.openstack.org/#/c/312687/ - LOG.warning('DEPRECATED: The `allowed_origin` keyword argument in ' - '`add_origin()` should be a list, found String.') - allowed_origin = [allowed_origin] - - if allowed_origin: - for origin in allowed_origin: - - if origin in self.allowed_origins: - LOG.warning('Allowed origin [%s] already exists, skipping' - % (allowed_origin,)) - continue - - self.allowed_origins[origin] = { - 'allow_credentials': allow_credentials, - 'expose_headers': expose_headers, - 'max_age': max_age, - 'allow_methods': allow_methods, - 'allow_headers': allow_headers - } - - def set_latent(self, allow_headers=None, allow_methods=None, - expose_headers=None): - '''Add a new latent property for this middleware. - - Latent properties are those values which a system requires for - operation. API-specific headers, for example, may be added by an - engineer so that they ship with the codebase, and thus do not require - extra documentation or passing of institutional knowledge. - - :param allow_headers: HTTP headers permitted in client requests. - :param allow_methods: HTTP methods permitted in client requests. - :param expose_headers: HTTP Headers exposed to clients. - ''' - - if allow_headers: - if isinstance(allow_headers, list): - self._latent_configuration['allow_headers'] = allow_headers - else: - raise TypeError("allow_headers must be a list or None.") - - if expose_headers: - if isinstance(expose_headers, list): - self._latent_configuration['expose_headers'] = expose_headers - else: - raise TypeError("expose_headers must be a list or None.") - - if allow_methods: - if isinstance(allow_methods, list): - self._latent_configuration['methods'] = allow_methods - else: - raise TypeError("allow_methods parameter must be a list or" - " None.") - - def process_response(self, response, request=None): - '''Check for CORS headers, and decorate if necessary. - - Perform two checks. First, if an OPTIONS request was issued, let the - application handle it, and (if necessary) decorate the response with - preflight headers. In this case, if a 404 is thrown by the underlying - application (i.e. if the underlying application does not handle - OPTIONS requests, the response code is overridden. - - In the case of all other requests, regular request headers are applied. - ''' - - # Sanity precheck: If we detect CORS headers provided by something in - # in the middleware chain, assume that it knows better. - if 'Access-Control-Allow-Origin' in response.headers: - return response - - # Doublecheck for an OPTIONS request. - if request.method == 'OPTIONS': - return self._apply_cors_preflight_headers(request=request, - response=response) - - # Apply regular CORS headers. - self._apply_cors_request_headers(request=request, response=response) - - # Finally, return the response. - return response - - @staticmethod - def _split_header_values(request, header_name): - """Convert a comma-separated header value into a list of values.""" - values = [] - if header_name in request.headers: - for value in request.headers[header_name].rsplit(','): - value = value.strip() - if value: - values.append(value) - return values - - def _apply_cors_preflight_headers(self, request, response): - """Handle CORS Preflight (Section 6.2) - - Given a request and a response, apply the CORS preflight headers - appropriate for the request. - """ - - # If the response contains a 2XX code, we have to assume that the - # underlying middleware's response content needs to be persisted. - # Otherwise, create a new response. - if 200 > response.status_code or response.status_code >= 300: - response = base.NoContentTypeResponse(status=webob.exc.HTTPOk.code) - - # Does the request have an origin header? (Section 6.2.1) - if 'Origin' not in request.headers: - return response - - # Is this origin registered? (Section 6.2.2) - try: - origin, cors_config = self._get_cors_config_by_origin( - request.headers['Origin']) - except InvalidOriginError: - return response - - # If there's no request method, exit. (Section 6.2.3) - if 'Access-Control-Request-Method' not in request.headers: - LOG.debug('CORS request does not contain ' - 'Access-Control-Request-Method header.') - return response - request_method = request.headers['Access-Control-Request-Method'] - - # Extract Request headers. If parsing fails, exit. (Section 6.2.4) - try: - request_headers = \ - self._split_header_values(request, - 'Access-Control-Request-Headers') - except Exception: - LOG.debug('Cannot parse request headers.') - return response - - # Compare request method to permitted methods (Section 6.2.5) - permitted_methods = ( - cors_config['allow_methods'] + - self._latent_configuration['methods'] - ) - if request_method not in permitted_methods: - LOG.debug('Request method \'%s\' not in permitted list: %s' - % (request_method, permitted_methods)) - return response - - # Compare request headers to permitted headers, case-insensitively. - # (Section 6.2.6) - permitted_headers = [header.upper() for header in - (cors_config['allow_headers'] + - self.simple_headers + - self._latent_configuration['allow_headers'])] - for requested_header in request_headers: - upper_header = requested_header.upper() - if upper_header not in permitted_headers: - LOG.debug('Request header \'%s\' not in permitted list: %s' - % (requested_header, permitted_headers)) - return response - - # Set the default origin permission headers. (Sections 6.2.7, 6.4) - response.headers['Vary'] = 'Origin' - response.headers['Access-Control-Allow-Origin'] = origin - - # Does this CORS configuration permit credentials? (Section 6.2.7) - if cors_config['allow_credentials']: - response.headers['Access-Control-Allow-Credentials'] = 'true' - - # Attach Access-Control-Max-Age if appropriate. (Section 6.2.8) - if 'max_age' in cors_config and cors_config['max_age']: - response.headers['Access-Control-Max-Age'] = \ - str(cors_config['max_age']) - - # Attach Access-Control-Allow-Methods. (Section 6.2.9) - response.headers['Access-Control-Allow-Methods'] = request_method - - # Attach Access-Control-Allow-Headers. (Section 6.2.10) - if request_headers: - response.headers['Access-Control-Allow-Headers'] = \ - ','.join(request_headers) - - return response - - def _get_cors_config_by_origin(self, origin): - if origin not in self.allowed_origins: - if '*' in self.allowed_origins: - origin = '*' - else: - LOG.debug('CORS request from origin \'%s\' not permitted.' - % origin) - raise InvalidOriginError(origin) - return origin, self.allowed_origins[origin] - - def _apply_cors_request_headers(self, request, response): - """Handle Basic CORS Request (Section 6.1) - - Given a request and a response, apply the CORS headers appropriate - for the request to the response. - """ - - # Does the request have an origin header? (Section 6.1.1) - if 'Origin' not in request.headers: - return - - # Is this origin registered? (Section 6.1.2) - try: - origin, cors_config = self._get_cors_config_by_origin( - request.headers['Origin']) - except InvalidOriginError: - return - - # Set the default origin permission headers. (Sections 6.1.3 & 6.4) - if 'Vary' in response.headers: - response.headers['Vary'] += ',Origin' - else: - response.headers['Vary'] = 'Origin' - response.headers['Access-Control-Allow-Origin'] = origin - - # Does this CORS configuration permit credentials? (Section 6.1.3) - if cors_config['allow_credentials']: - response.headers['Access-Control-Allow-Credentials'] = 'true' - - # Attach the exposed headers and exit. (Section 6.1.4) - if cors_config['expose_headers']: - response.headers['Access-Control-Expose-Headers'] = \ - ','.join(cors_config['expose_headers'] + - self._latent_configuration['expose_headers']) - -# NOTE(sileht): Shortcut for backwards compatibility -filter_factory = CORS.factory diff --git a/oslo_middleware/debug.py b/oslo_middleware/debug.py deleted file mode 100644 index fb2fc82..0000000 --- a/oslo_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 oslo_middleware import base - - -class Debug(base.ConfigurableMiddleware): - """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/healthcheck/__init__.py b/oslo_middleware/healthcheck/__init__.py deleted file mode 100644 index a821233..0000000 --- a/oslo_middleware/healthcheck/__init__.py +++ /dev/null @@ -1,377 +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. - -import collections -import gc -import json -import platform -import socket -import sys -import traceback - -import jinja2 -from oslo_utils import reflection -from oslo_utils import strutils -from oslo_utils import timeutils -import six -import stevedore -import webob.dec -import webob.exc -import webob.response - -try: - import greenlet -except ImportError: - greenlet = None - -from oslo_middleware import base - - -def _find_objects(t): - return [o for o in gc.get_objects() if isinstance(o, t)] - - -def _expand_template(contents, params): - tpl = jinja2.Template(source=contents, - undefined=jinja2.StrictUndefined) - return tpl.render(**params) - - -class Healthcheck(base.ConfigurableMiddleware): - """Healthcheck middleware used for monitoring. - - If the path is /healthcheck, it will respond 200 with "OK" as the body. - Or 503 with the reason as the body if one of the backend report - an application issue. - - This is useful for the following reasons: - - 1. Load balancers can 'ping' this url to determine service availability. - 2. Provides an endpoint that is similar to 'mod_status' in apache which - can provide details (or no details, depending on if configured) about - the activity of the server. - - Example requests/responses: - - $ curl -i -X HEAD "http://0.0.0.0:8775/status" - HTTP/1.1 204 No Content - Content-Type: text/plain; charset=UTF-8 - Content-Length: 0 - Date: Fri, 11 Sep 2015 18:55:08 GMT - - $ curl -i "http://0.0.0.0:8775/status" - HTTP/1.1 200 OK - Content-Type: text/plain; charset=UTF-8 - Content-Length: 2 - Date: Fri, 11 Sep 2015 18:55:43 GMT - - OK - - Example of paste configuration: - - .. code-block:: ini - - [filter:healthcheck] - paste.filter_factory = oslo_middleware:Healthcheck.factory - path = /healthcheck - backends = disable_by_file - disable_by_file_path = /var/run/nova/healthcheck_disable - - [pipeline:public_api] - pipeline = healthcheck sizelimit [...] public_service - - - Multiple filter sections can be defined if it desired to have - pipelines with different healthcheck configuration, example: - - .. code-block:: ini - - [pipeline:public_api] - pipeline = healthcheck_public sizelimit [...] public_service - - [pipeline:admin_api] - pipeline = healthcheck_admin sizelimit [...] admin_service - - [filter:healthcheck_public] - paste.filter_factory = oslo_middleware:Healthcheck.factory - path = /healthcheck_public - 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 - 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" - HEALTHY_TO_STATUS_CODES = { - True: webob.exc.HTTPOk.code, - False: webob.exc.HTTPServiceUnavailable.code, - } - HEAD_HEALTHY_TO_STATUS_CODES = { - True: webob.exc.HTTPNoContent.code, - False: webob.exc.HTTPServiceUnavailable.code, - } - PLAIN_RESPONSE_TEMPLATE = """ -{% for reason in reasons %} -{% if reason %}{{reason}}{% endif -%} -{% endfor %} -""" - - HTML_RESPONSE_TEMPLATE = """ - -Healthcheck Status - -{% if detailed -%} -

Server status

-{% if hostname -%} -Server hostname:
{{hostname|e}}
-{%- endif %} -Current time:
{{now|e}}
-Python version:
{{python_version|e}}
-Platform:
{{platform|e}}
-
-

Garbage collector:

-Counts:
{{gc.counts|e}}
-Thresholds:
{{gc.threshold|e}}
-
-{%- endif %} -

Result of {{results|length}} checks:

- - - -{% if detailed -%} - - - -{% else %} - -{%- endif %} - -{% for result in results -%} -{% if result.reason -%} - -{% if detailed -%} - -{%- endif %} - -{% if detailed -%} - -{%- endif %} - -{%- endif %} -{%- endfor %} - -
-Kind - -Reason - -Details - -Reason -
{{result.class|e}}{{result.reason|e}}{{result.details|e}}
-
-{% if detailed -%} -{% if greenthreads -%} -

{{greenthreads|length}} greenthread(s) active:

- - -{% for stack in greenthreads -%} - - - -{%- endfor %} - -
{{stack|e}}
-
-{%- endif %} -{% if threads -%} -

{{threads|length}} thread(s) active:

- - -{% for stack in threads -%} - - - -{%- endfor %} - -
{{stack|e}}
-{%- endif %} -{%- endif %} - - -""" - - 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(',') - self._backends = stevedore.NamedExtensionManager( - self.NAMESPACE, self._backend_names, - name_order=True, invoke_on_load=True, - invoke_args=(conf,)) - self._accept_to_functor = collections.OrderedDict([ - # Order here matters... - ('text/plain', self._make_text_response), - ('text/html', self._make_html_response), - ('application/json', self._make_json_response), - ]) - self._accept_order = tuple(six.iterkeys(self._accept_to_functor)) - # When no accept type matches instead of returning 406 we will - # always return text/plain (because sending an error from this - # middleware actually can cause issues). - self._default_accept = 'text/plain' - - @staticmethod - def _get_threadstacks(): - threadstacks = [] - try: - active_frames = sys._current_frames() - except AttributeError: - pass - else: - buf = six.StringIO() - for stack in six.itervalues(active_frames): - traceback.print_stack(stack, file=buf) - threadstacks.append(buf.getvalue()) - buf.seek(0) - buf.truncate() - return threadstacks - - @staticmethod - def _get_greenstacks(): - greenstacks = [] - if greenlet is not None: - buf = six.StringIO() - for gt in _find_objects(greenlet.greenlet): - traceback.print_stack(gt.gr_frame, file=buf) - greenstacks.append(buf.getvalue()) - buf.seek(0) - buf.truncate() - return greenstacks - - @staticmethod - def _pretty_json_dumps(contents): - return json.dumps(contents, indent=4, sort_keys=True) - - @staticmethod - def _are_results_healthy(results): - for result in results: - if not result.available: - return False - return True - - def _make_text_response(self, results, healthy): - params = { - 'reasons': [result.reason for result in results], - 'detailed': self._show_details, - } - body = _expand_template(self.PLAIN_RESPONSE_TEMPLATE, params) - return (body.strip(), 'text/plain') - - def _make_json_response(self, results, healthy): - if self._show_details: - body = { - 'detailed': True, - 'python_version': sys.version, - 'now': str(timeutils.utcnow()), - 'platform': platform.platform(), - 'gc': { - 'counts': gc.get_count(), - 'threshold': gc.get_threshold(), - }, - } - reasons = [] - for result in results: - reasons.append({ - 'reason': result.reason, - 'details': result.details or '', - 'class': reflection.get_class_name(result, - fully_qualified=False), - }) - body['reasons'] = reasons - body['greenthreads'] = self._get_greenstacks() - body['threads'] = self._get_threadstacks() - else: - body = { - 'reasons': [result.reason for result in results], - 'detailed': False, - } - return (self._pretty_json_dumps(body), 'application/json') - - def _make_head_response(self, results, healthy): - return ( "", "text/plain") - - def _make_html_response(self, results, healthy): - try: - hostname = socket.gethostname() - except socket.error: - hostname = None - translated_results = [] - for result in results: - translated_results.append({ - 'details': result.details or '', - 'reason': result.reason, - 'class': reflection.get_class_name(result, - fully_qualified=False), - }) - params = { - 'healthy': healthy, - 'hostname': hostname, - 'results': translated_results, - 'detailed': self._show_details, - 'now': str(timeutils.utcnow()), - 'python_version': sys.version, - 'platform': platform.platform(), - 'gc': { - 'counts': gc.get_count(), - 'threshold': gc.get_threshold(), - }, - 'threads': self._get_threadstacks(), - 'greenthreads': self._get_threadstacks(), - } - body = _expand_template(self.HTML_RESPONSE_TEMPLATE, params) - return (body.strip(), 'text/html') - - @webob.dec.wsgify - def process_request(self, req): - if req.path != self._path: - return None - results = [ext.obj.healthcheck(req.server_port) - for ext in self._backends] - healthy = self._are_results_healthy(results) - if req.method == "HEAD": - functor = self._make_head_response - status = self.HEAD_HEALTHY_TO_STATUS_CODES[healthy] - else: - status = self.HEALTHY_TO_STATUS_CODES[healthy] - accept_type = req.accept.best_match(self._accept_order) - if not accept_type: - accept_type = self._default_accept - functor = self._accept_to_functor[accept_type] - body, content_type = functor(results, healthy) - return webob.response.Response(status=status, body=body, - content_type=content_type) diff --git a/oslo_middleware/healthcheck/__main__.py b/oslo_middleware/healthcheck/__main__.py deleted file mode 100644 index 217fff6..0000000 --- a/oslo_middleware/healthcheck/__main__.py +++ /dev/null @@ -1,69 +0,0 @@ -# -# 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 argparse - -from six.moves import SimpleHTTPServer # noqa -from six.moves import socketserver -import webob - -from oslo_middleware import healthcheck - - -class HttpHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - def do_GET(self): - @webob.dec.wsgify - def dummy_application(req): - return 'test' - app = healthcheck.Healthcheck(dummy_application, {'detailed': True}) - req = webob.Request.blank("/healthcheck", accept='text/html', - method='GET') - res = req.get_response(app) - self.send_response(res.status_code) - for header_name, header_value in res.headerlist: - self.send_header(header_name, header_value) - self.end_headers() - self.wfile.write(res.body) - self.wfile.close() - - -def positive_int(blob): - value = int(blob) - if value < 0: - msg = "%r is not a positive integer" % blob - raise argparse.ArgumentTypeError(msg) - return value - - -def create_server(port=0): - handler = HttpHandler - server = socketserver.TCPServer(("", port), handler) - return server - - -def main(args=None): - """Runs a basic http server to show healthcheck functionality.""" - parser = argparse.ArgumentParser() - parser.add_argument("-p", "--port", - help="Unused port to run the tiny" - " http server on (or zero to select a" - " random unused port)", - type=positive_int, required=True) - args = parser.parse_args(args=args) - server = create_server(args.port) - print("Serving at port: %s" % server.server_address[1]) - server.serve_forever() - - -if __name__ == '__main__': - main() diff --git a/oslo_middleware/healthcheck/disable_by_file.py b/oslo_middleware/healthcheck/disable_by_file.py deleted file mode 100644 index 7fbb14b..0000000 --- a/oslo_middleware/healthcheck/disable_by_file.py +++ /dev/null @@ -1,111 +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. - -import logging -import os - -from oslo_middleware._i18n import _LW -from oslo_middleware.healthcheck import pluginbase - -LOG = logging.getLogger(__name__) - - -class DisableByFilesPortsHealthcheck(pluginbase.HealthcheckBaseExtension): - """DisableByFilesPorts healthcheck middleware plugin - - This plugin checks presence of a file that is provided for a application - running on a certain port to report if the service is unavailable - or not. - - Example of middleware configuration: - - .. code-block:: ini - - [filter:healthcheck] - paste.filter_factory = oslo_middleware:Healthcheck.factory - path = /healthcheck - backends = disable_by_files_ports - disable_by_file_paths = 5000:/var/run/keystone/healthcheck_disable, \ - 35357:/var/run/keystone/admin_healthcheck_disable - """ - def __init__(self, conf): - super(DisableByFilesPortsHealthcheck, self).__init__(conf) - self.status_files = {} - self.status_files.update( - self._iter_paths_ports(self.conf.get('disable_by_file_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) - - def healthcheck(self, server_port): - path = self.status_files.get(server_port) - if not path: - LOG.warning(_LW('DisableByFilesPorts healthcheck middleware' - ' enabled without disable_by_file_paths set' - ' for port %s') % server_port) - return pluginbase.HealthcheckResult(available=True, - reason="OK") - else: - if not os.path.exists(path): - return pluginbase.HealthcheckResult(available=True, - reason="OK") - else: - return pluginbase.HealthcheckResult(available=False, - reason="DISABLED BY FILE") - - -class DisableByFileHealthcheck(pluginbase.HealthcheckBaseExtension): - """DisableByFile healthcheck middleware plugin - - This plugin checks presence of a file to report if the service - is unavailable or not. - - Example of middleware configuration: - - .. code-block:: ini - - [filter:healthcheck] - paste.filter_factory = oslo_middleware:Healthcheck.factory - path = /healthcheck - backends = disable_by_file - disable_by_file_path = /var/run/nova/healthcheck_disable - """ - - def healthcheck(self, server_port): - path = self.conf.get('disable_by_file_path') - if path is None: - LOG.warning(_LW('DisableByFile healthcheck middleware enabled ' - 'without disable_by_file_path set')) - return pluginbase.HealthcheckResult( - available=True, reason="OK", - details="No 'disable_by_file_path' configuration value" - " specified") - elif not os.path.exists(path): - return pluginbase.HealthcheckResult( - available=True, reason="OK", - details="Path '%s' was not found" % path) - else: - return pluginbase.HealthcheckResult( - available=False, reason="DISABLED BY FILE", - details="Path '%s' was found" % path) diff --git a/oslo_middleware/healthcheck/pluginbase.py b/oslo_middleware/healthcheck/pluginbase.py deleted file mode 100644 index e370967..0000000 --- a/oslo_middleware/healthcheck/pluginbase.py +++ /dev/null @@ -1,40 +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. - -import abc - -import six - - -class HealthcheckResult(object): - """Result of a ``healthcheck`` method call should be this object.""" - - def __init__(self, available, reason, details=None): - self.available = available - self.reason = reason - self.details = details - - -@six.add_metaclass(abc.ABCMeta) -class HealthcheckBaseExtension(object): - def __init__(self, conf): - self.conf = conf - - @abc.abstractmethod - def healthcheck(self, server_port): - """method called by the healthcheck middleware - - return: HealthcheckResult object - """ diff --git a/oslo_middleware/http_proxy_to_wsgi.py b/oslo_middleware/http_proxy_to_wsgi.py deleted file mode 100644 index 84bc32b..0000000 --- a/oslo_middleware/http_proxy_to_wsgi.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- encoding: 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. -from debtcollector import removals -from oslo_config import cfg -from oslo_middleware import base - - -OPTS = [ - cfg.BoolOpt('enable_proxy_headers_parsing', - default=False, - help="Whether the application is behind a proxy or not. " - "This determines if the middleware should parse the " - "headers or not.") -] - - -class HTTPProxyToWSGI(base.ConfigurableMiddleware): - """HTTP proxy to WSGI termination middleware. - - This middleware overloads WSGI environment variables with the one provided - by the remote HTTP reverse proxy. - - """ - - def __init__(self, application, *args, **kwargs): - super(HTTPProxyToWSGI, self).__init__(application, *args, **kwargs) - self.oslo_conf.register_opts(OPTS, group='oslo_middleware') - - @staticmethod - def _parse_rfc7239_header(header): - """Parses RFC7239 Forward headers. - - e.g. for=192.0.2.60;proto=http, for=192.0.2.60;by=203.0.113.43 - - """ - result = [] - for proxy in header.split(","): - entry = {} - for d in proxy.split(";"): - key, _, value = d.partition("=") - entry[key.lower()] = value - result.append(entry) - return result - - def process_request(self, req): - if not self._conf_get('enable_proxy_headers_parsing'): - return - fwd_hdr = req.environ.get("HTTP_FORWARDED") - if fwd_hdr: - proxies = self._parse_rfc7239_header(fwd_hdr) - # Let's use the value from the first proxy - if proxies: - proxy = proxies[0] - - forwarded_proto = proxy.get("proto") - if forwarded_proto: - req.environ['wsgi.url_scheme'] = forwarded_proto - - forwarded_host = proxy.get("host") - if forwarded_host: - req.environ['HTTP_HOST'] = forwarded_host - - else: - # World before RFC7239 - forwarded_proto = req.environ.get("HTTP_X_FORWARDED_PROTO") - if forwarded_proto: - req.environ['wsgi.url_scheme'] = forwarded_proto - - forwarded_host = req.environ.get("HTTP_X_FORWARDED_HOST") - if forwarded_host: - req.environ['HTTP_HOST'] = forwarded_host - - v = req.environ.get("HTTP_X_FORWARDED_PREFIX") - if v: - req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME'] - - -@removals.remove -class HTTPProxyToWSGIMiddleware(HTTPProxyToWSGI): - """Placeholder for backward compatibility""" diff --git a/oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware-log-error.po b/oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware-log-error.po deleted file mode 100644 index 6ba0ef2..0000000 --- a/oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware-log-error.po +++ /dev/null @@ -1,27 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# -# Translators: -# Andreas Jaeger , 2014 -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 23:53+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2014-09-07 08:10+0000\n" -"Last-Translator: Andreas Jaeger \n" -"Language: de\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: German\n" - -#, python-format -msgid "An error occurred during processing the request: %s" -msgstr "Ein Fehler trat auf während die Anfrage behandelt wurde: %s" diff --git a/oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware.po b/oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware.po deleted file mode 100644 index 5176208..0000000 --- a/oslo_middleware/locale/de/LC_MESSAGES/oslo_middleware.po +++ /dev/null @@ -1,26 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# -# Translators: -# Andreas Jaeger , 2014 -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 23:53+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2014-09-07 08:09+0000\n" -"Last-Translator: Andreas Jaeger \n" -"Language: de\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: German\n" - -msgid "Request is too large." -msgstr "Die Anfrage ist zu groß." diff --git a/oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware-log-error.po b/oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware-log-error.po deleted file mode 100644 index 5a24b6e..0000000 --- a/oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware-log-error.po +++ /dev/null @@ -1,27 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# -# Translators: -# Andi Chandler , 2014 -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 23:53+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2014-11-03 11:03+0000\n" -"Last-Translator: Andi Chandler \n" -"Language: en-GB\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: English (United Kingdom)\n" - -#, python-format -msgid "An error occurred during processing the request: %s" -msgstr "An error occurred during processing the request: %s" diff --git a/oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware.po b/oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware.po deleted file mode 100644 index 84e7a10..0000000 --- a/oslo_middleware/locale/en_GB/LC_MESSAGES/oslo_middleware.po +++ /dev/null @@ -1,26 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# -# Translators: -# Andi Chandler , 2014 -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 23:53+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2014-11-03 11:03+0000\n" -"Last-Translator: Andi Chandler \n" -"Language: en-GB\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: English (United Kingdom)\n" - -msgid "Request is too large." -msgstr "Request is too large." diff --git a/oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware-log-error.po b/oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware-log-error.po deleted file mode 100644 index 4b90d25..0000000 --- a/oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware-log-error.po +++ /dev/null @@ -1,27 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# -# Translators: -# Maxime COQUEREL , 2014 -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 23:53+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2014-09-25 09:01+0000\n" -"Last-Translator: Maxime COQUEREL \n" -"Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: French\n" - -#, python-format -msgid "An error occurred during processing the request: %s" -msgstr "Une erreur s'est produite lors du traitement de la demande: %s" diff --git a/oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware.po b/oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware.po deleted file mode 100644 index 7d15ab3..0000000 --- a/oslo_middleware/locale/fr/LC_MESSAGES/oslo_middleware.po +++ /dev/null @@ -1,26 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# -# Translators: -# Maxime COQUEREL , 2014 -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 23:53+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2014-09-17 09:06+0000\n" -"Last-Translator: Maxime COQUEREL \n" -"Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: French\n" - -msgid "Request is too large." -msgstr "Demande trop importante." diff --git a/oslo_middleware/locale/oslo_middleware-log-error.pot b/oslo_middleware/locale/oslo_middleware-log-error.pot deleted file mode 100644 index 6df63c0..0000000 --- a/oslo_middleware/locale/oslo_middleware-log-error.pot +++ /dev/null @@ -1,25 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2016 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# FIRST AUTHOR , 2016. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-21 06:02+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.2.0\n" - -#: oslo_middleware/catch_errors.py:40 -#, python-format -msgid "An error occurred during processing the request: %s" -msgstr "" - diff --git a/oslo_middleware/locale/oslo_middleware.pot b/oslo_middleware/locale/oslo_middleware.pot deleted file mode 100644 index 009ca69..0000000 --- a/oslo_middleware/locale/oslo_middleware.pot +++ /dev/null @@ -1,25 +0,0 @@ -# Translations template for oslo.middleware. -# Copyright (C) 2016 ORGANIZATION -# This file is distributed under the same license as the oslo.middleware -# project. -# FIRST AUTHOR , 2016. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: oslo.middleware 3.7.1.dev18\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-21 06:02+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.2.0\n" - -#: oslo_middleware/sizelimit.py:59 oslo_middleware/sizelimit.py:73 -#: oslo_middleware/sizelimit.py:90 -msgid "Request is too large." -msgstr "" - diff --git a/oslo_middleware/opts.py b/oslo_middleware/opts.py deleted file mode 100644 index e66e723..0000000 --- a/oslo_middleware/opts.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright 2014 IBM Corp. -# -# 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__ = [ - 'list_opts', - 'list_opts_sizelimit', - 'list_opts_ssl', - 'list_opts_cors', - 'list_opts_http_proxy_to_wsgi', -] - - -import copy -import itertools - -from oslo_middleware import cors -from oslo_middleware import http_proxy_to_wsgi -from oslo_middleware import sizelimit -from oslo_middleware import ssl - - -def list_opts(): - """Return a list of oslo.config options for ALL of the middleware classes. - - 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 - """ - return list( - itertools.chain( - list_opts_sizelimit(), - list_opts_ssl(), - list_opts_cors(), - list_opts_http_proxy_to_wsgi(), - ) - ) - - -def list_opts_sizelimit(): - """Return a list of oslo.config options for the sizelimit middleware. - - 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 - """ - return [ - ('oslo_middleware', copy.deepcopy(sizelimit._opts)), - ] - - -def list_opts_ssl(): - """Return a list of oslo.config options for the SSL middleware. - - 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 - """ - return [ - ('oslo_middleware', copy.deepcopy(ssl.OPTS)), - ] - - -def list_opts_cors(): - """Return a list of oslo.config options for the cors middleware. - - 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 - """ - return [ - ('cors', copy.deepcopy(cors.CORS_OPTS)), - ('cors.subdomain', copy.deepcopy(cors.CORS_OPTS)) - ] - - -def list_opts_http_proxy_to_wsgi(): - """Return a list of oslo.config options for http_proxy_to_wsgi. - - 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 - """ - return [ - ('oslo_middleware', copy.deepcopy(http_proxy_to_wsgi.OPTS)), - ] diff --git a/oslo_middleware/request_id.py b/oslo_middleware/request_id.py deleted file mode 100644 index 31a433b..0000000 --- a/oslo_middleware/request_id.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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.ConfigurableMiddleware): - """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 deleted file mode 100644 index bba9886..0000000 --- a/oslo_middleware/sizelimit.py +++ /dev/null @@ -1,95 +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 oslo_middleware._i18n import _ -from oslo_middleware import base - - -_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) -] - - -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.ConfigurableMiddleware): - """Limit the size of incoming requests.""" - - def __init__(self, application, conf=None): - super(RequestBodySizeLimiter, self).__init__(application, conf) - self.oslo_conf.register_opts(_opts, group='oslo_middleware') - - @webob.dec.wsgify - def __call__(self, req): - max_size = self._conf_get('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/ssl.py b/oslo_middleware/ssl.py deleted file mode 100644 index 7853dc1..0000000 --- a/oslo_middleware/ssl.py +++ /dev/null @@ -1,48 +0,0 @@ -# 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 debtcollector import removals -from oslo_config import cfg -from oslo_middleware import base - - -OPTS = [ - cfg.StrOpt('secure_proxy_ssl_header', - default='X-Forwarded-Proto', - deprecated_for_removal=True, - help="The HTTP Header that will be used to determine what " - "the original request protocol scheme was, even if it was " - "hidden by an SSL termination proxy.") -] - - -removals.removed_module(__name__, - "oslo_middleware.http_proxy_to_wsgi") - - -class SSLMiddleware(base.ConfigurableMiddleware): - """SSL termination proxies middleware. - - This middleware overloads wsgi.url_scheme with the one provided in - secure_proxy_ssl_header header. This is useful when behind a SSL - termination proxy. - """ - - def __init__(self, application, *args, **kwargs): - super(SSLMiddleware, self).__init__(application, *args, **kwargs) - self.oslo_conf.register_opts(OPTS, group='oslo_middleware') - - def process_request(self, req): - self.header_name = 'HTTP_{0}'.format( - self._conf_get('secure_proxy_ssl_header').upper() - .replace('-', '_')) - req.environ['wsgi.url_scheme'] = req.environ.get( - self.header_name, req.environ['wsgi.url_scheme']) diff --git a/oslo_middleware/tests/__init__.py b/oslo_middleware/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/oslo_middleware/tests/test_base.py b/oslo_middleware/tests/test_base.py deleted file mode 100644 index 425ff9a..0000000 --- a/oslo_middleware/tests/test_base.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. -# -# 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 webob - -from oslo_middleware.base import ConfigurableMiddleware -from oslo_middleware.base import Middleware -from oslotest.base import BaseTestCase - - -@webob.dec.wsgify -def application(req): - return 'Hello, World!!!' - - -class TestBase(BaseTestCase): - """Test the base middleware class.""" - - def test_extend_with_request(self): - """Assert that a newer middleware behaves as appropriate. - - This tests makes sure that the request is passed to the - middleware's implementation. - """ - # Bootstrap the application - self.application = RequestBase(application) - - # Send a request through. - request = webob.Request({}, method='GET') - request.get_response(self.application) - - self.assertTrue(self.application.called_with_request) - - def test_extend_without_request(self): - """Assert that an older middleware behaves as appropriate. - - This tests makes sure that the request method is NOT passed to the - middleware's implementation, and that there are no other expected - errors. - """ - # Bootstrap the application - self.application = NoRequestBase(application) - - # Send a request through. - request = webob.Request({}, method='GET') - request.get_response(self.application) - - self.assertTrue(self.application.called_without_request) - - def test_no_content_type_added(self): - class TestMiddleware(Middleware): - @staticmethod - def process_request(req): - return "foobar" - - m = TestMiddleware(None) - request = webob.Request({}, method='GET') - response = request.get_response(m) - self.assertNotIn('Content-Type', response.headers) - - def test_paste_deploy_legacy(self): - app = LegacyMiddlewareTest.factory( - {'global': True}, local=True)(application) - self.assertEqual(app.conf, {}) - - def test_paste_deploy_configurable(self): - app = ConfigurableMiddlewareTest.factory( - {'global': True}, local=True)(application) - self.assertEqual(app.conf, {'global': True, 'local': True}) - - -class NoRequestBase(Middleware): - """Test middleware, implements old model.""" - def process_response(self, response): - self.called_without_request = True - return response - - -class RequestBase(Middleware): - """Test middleware, implements new model.""" - def process_response(self, response, request): - self.called_with_request = True - return response - - -class ConfigurableMiddlewareTest(ConfigurableMiddleware): - pass - - -class LegacyMiddlewareTest(Middleware): - pass diff --git a/oslo_middleware/tests/test_catch_errors.py b/oslo_middleware/tests/test_catch_errors.py deleted file mode 100644 index 920bbe2..0000000 --- a/oslo_middleware/tests/test_catch_errors.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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 deleted file mode 100644 index 6dde5d8..0000000 --- a/oslo_middleware/tests/test_correlation_id.py +++ /dev/null @@ -1,53 +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. - -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_cors.py b/oslo_middleware/tests/test_cors.py deleted file mode 100644 index 5e0822f..0000000 --- a/oslo_middleware/tests/test_cors.py +++ /dev/null @@ -1,1371 +0,0 @@ -# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. -# -# 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 fixture -from oslotest import base as test_base -import webob -import webob.dec -import webob.exc as exc - -from oslo_middleware import cors - - -@webob.dec.wsgify -def test_application(req): - if req.path_info == '/server_cors': - # Mirror back the origin in the request. - response = webob.Response(status=200) - response.headers['Access-Control-Allow-Origin'] = \ - req.headers['Origin'] - response.headers['X-Server-Generated-Response'] = '1' - return response - - if req.path_info == '/server_cors_vary': - # Mirror back the origin in the request. - response = webob.Response(status=200) - response.headers['Vary'] = 'Custom-Vary' - return response - - if req.path_info == '/server_no_cors': - # Send a response with no CORS headers. - response = webob.Response(status=200) - return response - - if req.method == 'OPTIONS': - raise exc.HTTPNotFound() - - return 'Hello World' - - -class CORSTestBase(test_base.BaseTestCase): - """Base class for all CORS tests. - - Sets up applications and helper methods. - """ - - def setUp(self): - """Setup the tests.""" - super(CORSTestBase, self).setUp() - - # Set up the config fixture. - self.config_fixture = self.useFixture(fixture.Config()) - self.config = self.config_fixture.conf - - def assertCORSResponse(self, response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - vary='Origin', - has_content_type=False): - """Test helper for CORS response headers. - - Assert all the headers in a given response. By default, we assume - the response is empty. - """ - - # Assert response status. - self.assertEqual(response.status, status) - - # Assert the Access-Control-Allow-Origin header. - self.assertHeader(response, - 'Access-Control-Allow-Origin', - allow_origin) - - # Assert the Access-Control-Max-Age header. - self.assertHeader(response, - 'Access-Control-Max-Age', - max_age) - - # Assert the Access-Control-Allow-Methods header. - self.assertHeader(response, - 'Access-Control-Allow-Methods', - allow_methods) - - # Assert the Access-Control-Allow-Headers header. - self.assertHeader(response, - 'Access-Control-Allow-Headers', - allow_headers) - - # Assert the Access-Control-Allow-Credentials header. - self.assertHeader(response, - 'Access-Control-Allow-Credentials', - allow_credentials) - - # Assert the Access-Control-Expose-Headers header. - self.assertHeader(response, - 'Access-Control-Expose-Headers', - expose_headers) - - # Assert no Content-Type added. - if not has_content_type: - self.assertHeader(response, 'Content-Type') - - # If we're expecting an origin response, also assert that the - # Vary: Origin header is set, since this implementation of the CORS - # specification permits multiple origin domains. - if allow_origin: - self.assertHeader(response, 'Vary', vary) - - def assertHeader(self, response, header, value=None): - if value: - self.assertIn(header, response.headers) - self.assertEqual(str(value), - response.headers[header]) - else: - self.assertNotIn(header, response.headers) - - -class CORSTestDefaultOverrides(CORSTestBase): - def setUp(self): - super(CORSTestDefaultOverrides, self).setUp() - - fixture = self.config_fixture # Line length accommodation - - fixture.load_raw_values(group='cors', - allowed_origin='http://valid.example.com') - - fixture.load_raw_values(group='cors.override_creds', - allowed_origin='http://creds.example.com', - allow_credentials='True') - - fixture.load_raw_values(group='cors.override_headers', - allowed_origin='http://headers.example.com', - expose_headers='X-Header-1,X-Header-2', - allow_headers='X-Header-1,X-Header-2') - - self.override_opts = { - 'expose_headers': ['X-Header-1'], - 'allow_headers': ['X-Header-2'], - 'allow_methods': ['GET', 'DELETE'], - 'allow_credentials': False, - 'max_age': 10 - } - - def test_config_defaults(self): - """Assert that using set_defaults overrides the appropriate values.""" - - cors.set_defaults(**self.override_opts) - - for opt in cors.CORS_OPTS: - if opt.dest in self.override_opts: - self.assertEqual(opt.default, self.override_opts[opt.dest]) - - def test_invalid_default_option(self): - """Assert that using set_defaults only permits valid options.""" - - self.assertRaises(AttributeError, - cors.set_defaults, - allowed_origin='test') - - def test_cascading_override(self): - """Assert that using set_defaults overrides cors.* config values.""" - - # set defaults - cors.set_defaults(**self.override_opts) - - # Now that the config is set up, create our application. - self.application = cors.CORS(test_application, self.config) - - # Check the global configuration for expected values: - gc = self.config.cors - self.assertEqual(gc.allowed_origin, ['http://valid.example.com']) - self.assertEqual(gc.allow_credentials, - self.override_opts['allow_credentials']) - self.assertEqual(gc.expose_headers, - self.override_opts['expose_headers']) - self.assertEqual(gc.max_age, 10) - self.assertEqual(gc.allow_methods, - self.override_opts['allow_methods']) - self.assertEqual(gc.allow_headers, - self.override_opts['allow_headers']) - - # Check the child configuration for expected values: - cc = self.config['cors.override_creds'] - self.assertEqual(cc.allowed_origin, ['http://creds.example.com']) - self.assertTrue(cc.allow_credentials) - self.assertEqual(cc.expose_headers, - self.override_opts['expose_headers']) - self.assertEqual(cc.max_age, 10) - self.assertEqual(cc.allow_methods, - self.override_opts['allow_methods']) - self.assertEqual(cc.allow_headers, - self.override_opts['allow_headers']) - - # Check the other child configuration for expected values: - ec = self.config['cors.override_headers'] - self.assertEqual(ec.allowed_origin, ['http://headers.example.com']) - self.assertEqual(ec.allow_credentials, - self.override_opts['allow_credentials']) - self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2']) - self.assertEqual(ec.max_age, 10) - self.assertEqual(ec.allow_methods, - self.override_opts['allow_methods']) - self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2']) - - -class CORSTestFilterFactory(CORSTestBase): - """Test the CORS filter_factory method.""" - - def test_filter_factory(self): - self.config([]) - - # Test a valid filter. - filter = cors.filter_factory(None, - allowed_origin='http://valid.example.com', - allow_credentials='False', - max_age='', - expose_headers='', - allow_methods='GET', - allow_headers='') - application = filter(test_application) - - self.assertIn('http://valid.example.com', application.allowed_origins) - - config = application.allowed_origins['http://valid.example.com'] - self.assertEqual(False, config['allow_credentials']) - self.assertIsNone(config['max_age']) - self.assertEqual([], config['expose_headers']) - self.assertEqual(['GET'], config['allow_methods']) - self.assertEqual([], config['allow_headers']) - - def test_filter_factory_multiorigin(self): - self.config([]) - - # Test a valid filter. - filter = cors.filter_factory(None, - allowed_origin='http://valid.example.com,' - 'http://other.example.com') - application = filter(test_application) - - self.assertIn('http://valid.example.com', application.allowed_origins) - self.assertIn('http://other.example.com', application.allowed_origins) - - def test_no_origin_fail(self): - '''Assert that a filter factory with no allowed_origin fails.''' - self.assertRaises(TypeError, - cors.filter_factory, - global_conf=None, - # allowed_origin=None, # Expected value. - allow_credentials='False', - max_age='', - expose_headers='', - allow_methods='GET', - allow_headers='') - - def test_no_origin_but_oslo_config_project(self): - '''Assert that a filter factory with oslo_config_project succeed.''' - cors.filter_factory(global_conf=None, oslo_config_project='foobar') - - def test_cor_config_sections_with_defaults(self): - '''Assert cors.* config sections with default values work.''' - - # Set up the config fixture. - self.config_fixture.load_raw_values(group='cors.subdomain') - - # Now that the config is set up, create our application. - self.application = cors.CORS(test_application, self.config) - - def test_factory_latent_properties(self): - '''Assert latent properties in paste.ini config. - - If latent_* properties are added to a paste.ini config, assert that - they are persisted in the middleware. - ''' - - # Spaces in config are deliberate to frobb the config parsing. - filter = cors.filter_factory(global_conf=None, - oslo_config_project='foobar', - latent_expose_headers=' X-Header-1 , X-2', - latent_allow_headers='X-Header-1 , X-2', - latent_allow_methods='GET,PUT, POST') - app = filter(test_application) - - # Ensure that the properties are in latent configuration. - self.assertEqual(['X-Header-1', 'X-2'], - app._latent_configuration['expose_headers']) - self.assertEqual(['X-Header-1', 'X-2'], - app._latent_configuration['allow_headers']) - self.assertEqual(['GET', 'PUT', 'POST'], - app._latent_configuration['methods']) - - -class CORSRegularRequestTest(CORSTestBase): - """CORS Specification Section 6.1 - - http://www.w3.org/TR/cors/#resource-requests - """ - - # List of HTTP methods (other than OPTIONS) to test with. - methods = ['POST', 'PUT', 'DELETE', 'GET', 'TRACE', 'HEAD'] - - def setUp(self): - """Setup the tests.""" - super(CORSRegularRequestTest, self).setUp() - - fixture = self.config_fixture # Line length accommodation - fixture.load_raw_values(group='cors', - allowed_origin='http://valid.example.com', - allow_credentials='False', - max_age='', - expose_headers='', - allow_methods='GET', - allow_headers='') - - fixture.load_raw_values(group='cors.credentials', - allowed_origin='http://creds.example.com', - allow_credentials='True') - - fixture.load_raw_values(group='cors.exposed-headers', - allowed_origin='http://headers.example.com', - expose_headers='X-Header-1,X-Header-2', - allow_headers='X-Header-1,X-Header-2') - - fixture.load_raw_values(group='cors.cached', - allowed_origin='http://cached.example.com', - max_age='3600') - - fixture.load_raw_values(group='cors.get-only', - allowed_origin='http://get.example.com', - allow_methods='GET') - fixture.load_raw_values(group='cors.all-methods', - allowed_origin='http://all.example.com', - allow_methods='GET,PUT,POST,DELETE,HEAD') - - fixture.load_raw_values(group='cors.duplicate', - allowed_origin='http://domain1.example.com,' - 'http://domain2.example.com') - - # Now that the config is set up, create our application. - self.application = cors.CORS(test_application, self.config) - - def test_config_overrides(self): - """Assert that the configuration options are properly registered.""" - - # Confirm global configuration - gc = self.config.cors - 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.assertEqual(gc.allow_methods, ['GET']) - self.assertEqual(gc.allow_headers, []) - - # Confirm credentials overrides. - cc = self.config['cors.credentials'] - self.assertEqual(cc.allowed_origin, ['http://creds.example.com']) - self.assertEqual(cc.allow_credentials, True) - self.assertEqual(cc.expose_headers, gc.expose_headers) - self.assertEqual(cc.max_age, gc.max_age) - self.assertEqual(cc.allow_methods, gc.allow_methods) - self.assertEqual(cc.allow_headers, gc.allow_headers) - - # Confirm exposed-headers overrides. - ec = self.config['cors.exposed-headers'] - self.assertEqual(ec.allowed_origin, ['http://headers.example.com']) - self.assertEqual(ec.allow_credentials, gc.allow_credentials) - self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2']) - self.assertEqual(ec.max_age, gc.max_age) - self.assertEqual(ec.allow_methods, gc.allow_methods) - self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2']) - - # Confirm cached overrides. - chc = self.config['cors.cached'] - self.assertEqual(chc.allowed_origin, ['http://cached.example.com']) - self.assertEqual(chc.allow_credentials, gc.allow_credentials) - self.assertEqual(chc.expose_headers, gc.expose_headers) - self.assertEqual(chc.max_age, 3600) - self.assertEqual(chc.allow_methods, gc.allow_methods) - self.assertEqual(chc.allow_headers, gc.allow_headers) - - # Confirm get-only overrides. - goc = self.config['cors.get-only'] - self.assertEqual(goc.allowed_origin, ['http://get.example.com']) - self.assertEqual(goc.allow_credentials, gc.allow_credentials) - self.assertEqual(goc.expose_headers, gc.expose_headers) - self.assertEqual(goc.max_age, gc.max_age) - self.assertEqual(goc.allow_methods, ['GET']) - self.assertEqual(goc.allow_headers, gc.allow_headers) - - # Confirm all-methods overrides. - ac = self.config['cors.all-methods'] - self.assertEqual(ac.allowed_origin, ['http://all.example.com']) - self.assertEqual(ac.allow_credentials, gc.allow_credentials) - self.assertEqual(ac.expose_headers, gc.expose_headers) - self.assertEqual(ac.max_age, gc.max_age) - self.assertEqual(ac.allow_methods, - ['GET', 'PUT', 'POST', 'DELETE', 'HEAD']) - self.assertEqual(ac.allow_headers, gc.allow_headers) - - # Confirm duplicate domains. - ac = self.config['cors.duplicate'] - self.assertEqual(ac.allowed_origin, ['http://domain1.example.com', - 'http://domain2.example.com']) - self.assertEqual(ac.allow_credentials, gc.allow_credentials) - self.assertEqual(ac.expose_headers, gc.expose_headers) - self.assertEqual(ac.max_age, gc.max_age) - self.assertEqual(ac.allow_methods, gc.allow_methods) - self.assertEqual(ac.allow_headers, gc.allow_headers) - - def test_no_origin_header(self): - """CORS Specification Section 6.1.1 - - If the Origin header is not present terminate this set of steps. The - request is outside the scope of this specification. - """ - for method in self.methods: - request = webob.Request.blank('/') - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - has_content_type=True) - - def test_origin_headers(self): - """CORS Specification Section 6.1.2 - - If the value of the Origin header is not a case-sensitive match for - any of the values in list of origins, do not set any additional - headers and terminate this set of steps. - """ - - # Test valid origin header. - for method in self.methods: - request = webob.Request.blank('/') - request.method = method - request.headers['Origin'] = 'http://valid.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://valid.example.com', - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - has_content_type=True) - - # Test origin header not present in configuration. - for method in self.methods: - request = webob.Request.blank('/') - request.method = method - request.headers['Origin'] = 'http://invalid.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - has_content_type=True) - - # Test valid, but case-mismatched origin header. - for method in self.methods: - request = webob.Request.blank('/') - request.method = method - request.headers['Origin'] = 'http://VALID.EXAMPLE.COM' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - has_content_type=True) - - # Test valid header from list of duplicates. - for method in self.methods: - request = webob.Request.blank('/') - request.method = method - request.headers['Origin'] = 'http://domain2.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://domain2.example.com', - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - has_content_type=True) - - def test_supports_credentials(self): - """CORS Specification Section 6.1.3 - - If the resource supports credentials add a single - Access-Control-Allow-Origin header, with the value of the Origin header - as value, and add a single Access-Control-Allow-Credentials header with - the case-sensitive string "true" as value. - - Otherwise, add a single Access-Control-Allow-Origin header, with - either the value of the Origin header or the string "*" as value. - - NOTE: We never use the "*" as origin. - """ - # Test valid origin header without credentials. - for method in self.methods: - request = webob.Request.blank('/') - request.method = method - request.headers['Origin'] = 'http://valid.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://valid.example.com', - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - has_content_type=True) - - # Test valid origin header with credentials - for method in self.methods: - request = webob.Request.blank('/') - request.method = method - request.headers['Origin'] = 'http://creds.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://creds.example.com', - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials="true", - expose_headers=None, - has_content_type=True) - - def test_expose_headers(self): - """CORS Specification Section 6.1.4 - - If the list of exposed headers is not empty add one or more - Access-Control-Expose-Headers headers, with as values the header field - names given in the list of exposed headers. - """ - for method in self.methods: - request = webob.Request.blank('/') - request.method = method - request.headers['Origin'] = 'http://headers.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://headers.example.com', - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers='X-Header-1,X-Header-2', - has_content_type=True) - - def test_application_options_response(self): - """Assert that an application provided OPTIONS response is honored. - - If the underlying application, via middleware or other, provides a - CORS response, its response should be honored. - """ - test_origin = 'http://creds.example.com' - - request = webob.Request.blank('/server_cors') - request.method = "GET" - request.headers['Origin'] = test_origin - request.headers['Access-Control-Request-Method'] = 'GET' - - response = request.get_response(self.application) - - # If the regular CORS handling catches this request, it should set - # the allow credentials header. This makes sure that it doesn't. - self.assertNotIn('Access-Control-Allow-Credentials', response.headers) - self.assertEqual(response.headers['Access-Control-Allow-Origin'], - test_origin) - self.assertEqual(response.headers['X-Server-Generated-Response'], - '1') - - def test_application_vary_respected(self): - """Assert that an application's provided Vary header is persisted. - - If the underlying application, via middleware or other, provides a - Vary header, its response should be honored. - """ - - request = webob.Request.blank('/server_cors_vary') - request.method = "GET" - request.headers['Origin'] = 'http://valid.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - - response = request.get_response(self.application) - - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://valid.example.com', - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None, - vary='Custom-Vary,Origin', - has_content_type=True) - - -class CORSPreflightRequestTest(CORSTestBase): - """CORS Specification Section 6.2 - - http://www.w3.org/TR/cors/#resource-preflight-requests - """ - - def setUp(self): - super(CORSPreflightRequestTest, self).setUp() - - fixture = self.config_fixture # Line length accommodation - fixture.load_raw_values(group='cors', - allowed_origin='http://valid.example.com', - allow_credentials='False', - max_age='', - expose_headers='', - allow_methods='GET', - allow_headers='') - - fixture.load_raw_values(group='cors.credentials', - allowed_origin='http://creds.example.com', - allow_credentials='True') - - fixture.load_raw_values(group='cors.exposed-headers', - allowed_origin='http://headers.example.com', - expose_headers='X-Header-1,X-Header-2', - allow_headers='X-Header-1,X-Header-2') - - fixture.load_raw_values(group='cors.cached', - allowed_origin='http://cached.example.com', - max_age='3600') - - fixture.load_raw_values(group='cors.get-only', - allowed_origin='http://get.example.com', - allow_methods='GET') - fixture.load_raw_values(group='cors.all-methods', - allowed_origin='http://all.example.com', - allow_methods='GET,PUT,POST,DELETE,HEAD') - - # Now that the config is set up, create our application. - self.application = cors.CORS(test_application, self.config) - - def test_config_overrides(self): - """Assert that the configuration options are properly registered.""" - - # Confirm global configuration - gc = self.config.cors - 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.assertEqual(gc.allow_methods, ['GET']) - self.assertEqual(gc.allow_headers, []) - - # Confirm credentials overrides. - cc = self.config['cors.credentials'] - self.assertEqual(cc.allowed_origin, ['http://creds.example.com']) - self.assertEqual(cc.allow_credentials, True) - self.assertEqual(cc.expose_headers, gc.expose_headers) - self.assertEqual(cc.max_age, gc.max_age) - self.assertEqual(cc.allow_methods, gc.allow_methods) - self.assertEqual(cc.allow_headers, gc.allow_headers) - - # Confirm exposed-headers overrides. - ec = self.config['cors.exposed-headers'] - self.assertEqual(ec.allowed_origin, ['http://headers.example.com']) - self.assertEqual(ec.allow_credentials, gc.allow_credentials) - self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2']) - self.assertEqual(ec.max_age, gc.max_age) - self.assertEqual(ec.allow_methods, gc.allow_methods) - self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2']) - - # Confirm cached overrides. - chc = self.config['cors.cached'] - self.assertEqual(chc.allowed_origin, ['http://cached.example.com']) - self.assertEqual(chc.allow_credentials, gc.allow_credentials) - self.assertEqual(chc.expose_headers, gc.expose_headers) - self.assertEqual(chc.max_age, 3600) - self.assertEqual(chc.allow_methods, gc.allow_methods) - self.assertEqual(chc.allow_headers, gc.allow_headers) - - # Confirm get-only overrides. - goc = self.config['cors.get-only'] - self.assertEqual(goc.allowed_origin, ['http://get.example.com']) - self.assertEqual(goc.allow_credentials, gc.allow_credentials) - self.assertEqual(goc.expose_headers, gc.expose_headers) - self.assertEqual(goc.max_age, gc.max_age) - self.assertEqual(goc.allow_methods, ['GET']) - self.assertEqual(goc.allow_headers, gc.allow_headers) - - # Confirm all-methods overrides. - ac = self.config['cors.all-methods'] - self.assertEqual(ac.allowed_origin, ['http://all.example.com']) - self.assertEqual(ac.allow_credentials, gc.allow_credentials) - self.assertEqual(ac.expose_headers, gc.expose_headers) - self.assertEqual(ac.max_age, gc.max_age) - self.assertEqual(ac.allow_methods, - ['GET', 'PUT', 'POST', 'DELETE', 'HEAD']) - self.assertEqual(ac.allow_headers, gc.allow_headers) - - def test_no_origin_header(self): - """CORS Specification Section 6.2.1 - - If the Origin header is not present terminate this set of steps. The - request is outside the scope of this specification. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_case_sensitive_origin(self): - """CORS Specification Section 6.2.2 - - If the value of the Origin header is not a case-sensitive match for - any of the values in list of origins do not set any additional headers - and terminate this set of steps. - """ - - # Test valid domain - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://valid.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://valid.example.com', - max_age=None, - allow_methods='GET', - allow_headers='', - allow_credentials=None, - expose_headers=None) - - # Test invalid domain - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://invalid.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - # Test case-sensitive mismatch domain - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://VALID.EXAMPLE.COM' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_simple_header_response(self): - """CORS Specification Section 3 - - A header is said to be a simple header if the header field name is an - ASCII case-insensitive match for Accept, Accept-Language, or - Content-Language or if it is an ASCII case-insensitive match for - Content-Type and the header field value media type (excluding - parameters) is an ASCII case-insensitive match for - application/x-www-form-urlencoded, multipart/form-data, or text/plain. - - NOTE: We are not testing the media type cases. - """ - - simple_headers = ','.join([ - 'accept', - 'accept-language', - 'content-language', - 'content-type' - ]) - - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://valid.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - request.headers['Access-Control-Request-Headers'] = simple_headers - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://valid.example.com', - max_age=None, - allow_methods='GET', - allow_headers=simple_headers, - allow_credentials=None, - expose_headers=None) - - def test_no_request_method(self): - """CORS Specification Section 6.2.3 - - If there is no Access-Control-Request-Method header or if parsing - failed, do not set any additional headers and terminate this set of - steps. The request is outside the scope of this specification. - """ - - # Test valid domain, valid method. - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://get.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://get.example.com', - max_age=None, - allow_methods='GET', - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - # Test valid domain, invalid HTTP method. - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://valid.example.com' - request.headers['Access-Control-Request-Method'] = 'TEAPOT' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - # Test valid domain, no HTTP method. - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://valid.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_invalid_method(self): - """CORS Specification Section 6.2.3 - - If method is not a case-sensitive match for any of the values in - list of methods do not set any additional headers and terminate this - set of steps. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://get.example.com' - request.headers['Access-Control-Request-Method'] = 'get' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_no_parse_request_headers(self): - """CORS Specification Section 6.2.4 - - If there are no Access-Control-Request-Headers headers let header - field-names be the empty list. - - If parsing failed do not set any additional headers and terminate - this set of steps. The request is outside the scope of this - specification. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://headers.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - request.headers['Access-Control-Request-Headers'] = 'value with spaces' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_no_request_headers(self): - """CORS Specification Section 6.2.4 - - If there are no Access-Control-Request-Headers headers let header - field-names be the empty list. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://headers.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - request.headers['Access-Control-Request-Headers'] = '' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://headers.example.com', - max_age=None, - allow_methods='GET', - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_request_headers(self): - """CORS Specification Section 6.2.4 - - Let header field-names be the values as result of parsing the - Access-Control-Request-Headers headers. - - If there are no Access-Control-Request-Headers headers let header - field-names be the empty list. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://headers.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - request.headers['Access-Control-Request-Headers'] = 'X-Header-1,' \ - 'X-Header-2' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://headers.example.com', - max_age=None, - allow_methods='GET', - allow_headers='X-Header-1,X-Header-2', - allow_credentials=None, - expose_headers=None) - - def test_request_headers_not_permitted(self): - """CORS Specification Section 6.2.4, 6.2.6 - - If there are no Access-Control-Request-Headers headers let header - field-names be the empty list. - - If any of the header field-names is not a ASCII case-insensitive - match for any of the values in list of headers do not set any - additional headers and terminate this set of steps. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://headers.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - request.headers['Access-Control-Request-Headers'] = 'X-Not-Exposed,' \ - 'X-Never-Exposed' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_credentials(self): - """CORS Specification Section 6.2.7 - - If the resource supports credentials add a single - Access-Control-Allow-Origin header, with the value of the Origin header - as value, and add a single Access-Control-Allow-Credentials header with - the case-sensitive string "true" as value. - - Otherwise, add a single Access-Control-Allow-Origin header, with either - the value of the Origin header or the string "*" as value. - - NOTE: We never use the "*" as origin. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://creds.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://creds.example.com', - max_age=None, - allow_methods='GET', - allow_headers=None, - allow_credentials="true", - expose_headers=None) - - def test_optional_max_age(self): - """CORS Specification Section 6.2.8 - - Optionally add a single Access-Control-Max-Age header with as value - the amount of seconds the user agent is allowed to cache the result of - the request. - """ - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://cached.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://cached.example.com', - max_age=3600, - allow_methods='GET', - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_allow_methods(self): - """CORS Specification Section 6.2.9 - - Add one or more Access-Control-Allow-Methods headers consisting of - (a subset of) the list of methods. - - Since the list of methods can be unbounded, simply returning the method - indicated by Access-Control-Request-Method (if supported) can be - enough. - """ - for method in ['GET', 'PUT', 'POST', 'DELETE']: - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://all.example.com' - request.headers['Access-Control-Request-Method'] = method - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://all.example.com', - max_age=None, - allow_methods=method, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - for method in ['PUT', 'POST', 'DELETE']: - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://get.example.com' - request.headers['Access-Control-Request-Method'] = method - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin=None, - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials=None, - expose_headers=None) - - def test_allow_headers(self): - """CORS Specification Section 6.2.10 - - Add one or more Access-Control-Allow-Headers headers consisting of - (a subset of) the list of headers. - - If each of the header field-names is a simple header and none is - Content-Type, this step may be skipped. - - If a header field name is a simple header and is not Content-Type, it - is not required to be listed. Content-Type is to be listed as only a - subset of its values makes it qualify as simple header. - """ - - requested_headers = 'Content-Type,X-Header-1,Cache-Control,Expires,' \ - 'Last-Modified,Pragma' - - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://headers.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - request.headers['Access-Control-Request-Headers'] = requested_headers - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://headers.example.com', - max_age=None, - allow_methods='GET', - allow_headers=requested_headers, - allow_credentials=None, - expose_headers=None) - - def test_application_options_response(self): - """Assert that an application provided OPTIONS response is honored. - - If the underlying application, via middleware or other, provides a - CORS response, its response should be honored. - """ - test_origin = 'http://creds.example.com' - - request = webob.Request.blank('/server_cors') - request.method = "OPTIONS" - request.headers['Origin'] = test_origin - request.headers['Access-Control-Request-Method'] = 'GET' - - response = request.get_response(self.application) - - # If the regular CORS handling catches this request, it should set - # the allow credentials header. This makes sure that it doesn't. - self.assertNotIn('Access-Control-Allow-Credentials', response.headers) - self.assertEqual(response.headers['Access-Control-Allow-Origin'], - test_origin) - self.assertEqual(response.headers['X-Server-Generated-Response'], - '1') - - # If the application returns an OPTIONS response without CORS - # headers, assert that we apply headers. - request = webob.Request.blank('/server_no_cors') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://get.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://get.example.com', - max_age=None, - allow_methods='GET', - allow_headers=None, - allow_credentials=None, - expose_headers=None, - has_content_type=True) - - -class CORSTestWildcard(CORSTestBase): - """Test the CORS wildcard specification.""" - - def setUp(self): - super(CORSTestWildcard, self).setUp() - - fixture = self.config_fixture # Line length accommodation - fixture.load_raw_values(group='cors', - allowed_origin='http://default.example.com', - allow_credentials='True', - max_age='', - expose_headers='', - allow_methods='GET,PUT,POST,DELETE,HEAD', - allow_headers='') - - fixture.load_raw_values(group='cors.wildcard', - allowed_origin='*', - allow_methods='GET') - - # Now that the config is set up, create our application. - self.application = cors.CORS(test_application, self.config) - - def test_config_overrides(self): - """Assert that the configuration options are properly registered.""" - - # Confirm global configuration - gc = self.config.cors - self.assertEqual(gc.allowed_origin, ['http://default.example.com']) - self.assertEqual(gc.allow_credentials, True) - self.assertEqual(gc.expose_headers, []) - self.assertEqual(gc.max_age, None) - self.assertEqual(gc.allow_methods, ['GET', 'PUT', 'POST', 'DELETE', - 'HEAD']) - self.assertEqual(gc.allow_headers, []) - - # Confirm all-methods overrides. - ac = self.config['cors.wildcard'] - self.assertEqual(ac.allowed_origin, ['*']) - self.assertEqual(gc.allow_credentials, True) - self.assertEqual(ac.expose_headers, gc.expose_headers) - self.assertEqual(ac.max_age, gc.max_age) - self.assertEqual(ac.allow_methods, ['GET']) - self.assertEqual(ac.allow_headers, gc.allow_headers) - - def test_wildcard_domain(self): - """CORS Specification, Wildcards - - If the configuration file specifies CORS settings for the wildcard '*' - domain, it should return those for all origin domains except for the - overrides. - """ - - # Test valid domain - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://default.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://default.example.com', - max_age=None, - allow_methods='GET', - allow_headers='', - allow_credentials='true', - expose_headers=None) - - # Test valid domain - request = webob.Request.blank('/') - request.method = "GET" - request.headers['Origin'] = 'http://default.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://default.example.com', - max_age=None, - allow_headers='', - allow_credentials='true', - expose_headers=None, - has_content_type=True) - - # Test invalid domain - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://invalid.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='*', - max_age=None, - allow_methods='GET', - allow_headers='', - allow_credentials='true', - expose_headers=None, - has_content_type=True) - - -class CORSTestLatentProperties(CORSTestBase): - """Test the CORS wildcard specification.""" - - def setUp(self): - super(CORSTestLatentProperties, self).setUp() - - fixture = self.config_fixture # Line length accommodation - fixture.load_raw_values(group='cors', - allowed_origin='http://default.example.com', - allow_credentials='True', - max_age='', - expose_headers='X-Configured', - allow_methods='GET', - allow_headers='X-Configured') - - # Now that the config is set up, create our application. - self.application = cors.CORS(test_application, self.config) - - def test_latent_methods(self): - """Assert that latent HTTP methods are permitted.""" - - self.application.set_latent(allow_headers=None, - expose_headers=None, - allow_methods=['POST']) - - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://default.example.com' - request.headers['Access-Control-Request-Method'] = 'POST' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://default.example.com', - max_age=None, - allow_methods='POST', - allow_headers='', - allow_credentials='true', - expose_headers=None) - - def test_invalid_latent_methods(self): - """Assert that passing a non-list is caught.""" - - self.assertRaises(TypeError, - self.application.set_latent, - allow_methods='POST') - - def test_latent_allow_headers(self): - """Assert that latent HTTP headers are permitted.""" - - self.application.set_latent(allow_headers=['X-Latent'], - expose_headers=None, - allow_methods=None) - - request = webob.Request.blank('/') - request.method = "OPTIONS" - request.headers['Origin'] = 'http://default.example.com' - request.headers['Access-Control-Request-Method'] = 'GET' - request.headers[ - 'Access-Control-Request-Headers'] = 'X-Latent,X-Configured' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://default.example.com', - max_age=None, - allow_methods='GET', - allow_headers='X-Latent,X-Configured', - allow_credentials='true', - expose_headers=None) - - def test_invalid_latent_allow_headers(self): - """Assert that passing a non-list is caught in allow headers.""" - - self.assertRaises(TypeError, - self.application.set_latent, - allow_headers='X-Latent') - - def test_latent_expose_headers(self): - """Assert that latent HTTP headers are exposed.""" - - self.application.set_latent(allow_headers=None, - expose_headers=[ - 'X-Server-Generated-Response'], - allow_methods=None) - - request = webob.Request.blank('/') - request.method = "GET" - request.headers['Origin'] = 'http://default.example.com' - response = request.get_response(self.application) - self.assertCORSResponse(response, - status='200 OK', - allow_origin='http://default.example.com', - max_age=None, - allow_methods=None, - allow_headers=None, - allow_credentials='true', - expose_headers='X-Configured,' - 'X-Server-Generated-Response', - has_content_type=True) - - def test_invalid_latent_expose_headers(self): - """Assert that passing a non-list is caught in expose headers.""" - - # Add headers to the application. - - self.assertRaises(TypeError, - self.application.set_latent, - expose_headers='X-Latent') diff --git a/oslo_middleware/tests/test_entry_points.py b/oslo_middleware/tests/test_entry_points.py deleted file mode 100644 index d7368ac..0000000 --- a/oslo_middleware/tests/test_entry_points.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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 -import stevedore -from testtools import matchers - - -class TestPasteDeploymentEntryPoints(base.BaseTestCase): - - def test_entry_points(self): - factory_classes = { - 'catch_errors': 'CatchErrors', - 'correlation_id': 'CorrelationId', - 'cors': 'CORS', - 'debug': 'Debug', - 'healthcheck': 'Healthcheck', - 'http_proxy_to_wsgi': 'HTTPProxyToWSGI', - 'request_id': 'RequestId', - 'sizelimit': 'RequestBodySizeLimiter', - 'ssl': 'SSLMiddleware', - } - - em = stevedore.ExtensionManager('paste.filter_factory') - - # Ensure all the factories are defined by their names - factory_names = [extension.name for extension in em] - self.assertThat(factory_names, - matchers.ContainsAll(factory_classes)) diff --git a/oslo_middleware/tests/test_healthcheck.py b/oslo_middleware/tests/test_healthcheck.py deleted file mode 100644 index df73969..0000000 --- a/oslo_middleware/tests/test_healthcheck.py +++ /dev/null @@ -1,189 +0,0 @@ -# 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 threading -import time - -import mock -from oslotest import base as test_base -import requests -import webob.dec -import webob.exc - -from oslo_middleware import healthcheck -from oslo_middleware.healthcheck import __main__ - - -class HealthcheckMainTests(test_base.BaseTestCase): - - def test_startup_response(self): - server = __main__.create_server(0) - th = threading.Thread(target=server.serve_forever) - th.start() - self.addCleanup(server.shutdown) - while True: - try: - # Connecting on 0.0.0.0 is not allowed on windows - # The operating system will return WSAEADDRNOTAVAIL which - # in turn will throw a requests.ConnectionError - r = requests.get("http://127.0.0.1:%s" % ( - server.server_address[1])) - except requests.ConnectionError: - # Server hasn't started up yet, try again in a few. - time.sleep(1) - else: - self.assertEqual(200, r.status_code) - break - - -class HealthcheckTests(test_base.BaseTestCase): - - @staticmethod - @webob.dec.wsgify - def application(req): - return 'Hello, World!!!' - - def _do_test_request(self, conf={}, path='/healthcheck', - accept='text/plain', method='GET', - server_port=80): - self.app = healthcheck.Healthcheck(self.application, conf) - req = webob.Request.blank(path, accept=accept, method=method) - req.server_port = server_port - res = req.get_response(self.app) - return res - - def _do_test(self, conf={}, path='/healthcheck', - expected_code=webob.exc.HTTPOk.code, - expected_body=b'', accept='text/plain', - method='GET', server_port=80): - res = self._do_test_request(conf=conf, path=path, - accept=accept, method=method, - server_port=server_port) - self.assertEqual(expected_code, res.status_int) - self.assertEqual(expected_body, res.body) - - def test_default_path_match(self): - self._do_test() - - def test_default_path_not_match(self): - self._do_test(path='/toto', expected_body=b'Hello, World!!!') - - def test_configured_path_match(self): - conf = {'path': '/hidden_healthcheck'} - self._do_test(conf, path='/hidden_healthcheck') - - def test_configured_path_not_match(self): - conf = {'path': '/hidden_healthcheck'} - self._do_test(conf, path='/toto', expected_body=b'Hello, World!!!') - - @mock.patch('oslo_middleware.healthcheck.disable_by_file.LOG') - def test_disablefile_unconfigured(self, fake_log): - fake_warn = fake_log.warning - conf = {'backends': 'disable_by_file'} - self._do_test(conf, expected_body=b'OK') - self.assertIn('disable_by_file', self.app._backends.names()) - fake_warn.assert_called_once_with( - 'DisableByFile healthcheck middleware ' - 'enabled without disable_by_file_path ' - 'set' - ) - - def test_disablefile_enabled(self): - conf = {'backends': 'disable_by_file', - 'disable_by_file_path': '/foobar'} - self._do_test(conf, expected_body=b'OK') - self.assertIn('disable_by_file', self.app._backends.names()) - - def test_disablefile_enabled_head(self): - conf = {'backends': 'disable_by_file', - 'disable_by_file_path': '/foobar'} - self._do_test(conf, expected_body=b'', method='HEAD', - expected_code=webob.exc.HTTPNoContent.code) - - def test_disablefile_enabled_html_detailed(self): - conf = {'backends': 'disable_by_file', - 'disable_by_file_path': '/foobar', 'detailed': True} - res = self._do_test_request(conf, accept="text/html") - self.assertIn(b'Result of 1 checks:', res.body) - self.assertIn(b'OK', res.body) - self.assertEqual(webob.exc.HTTPOk.code, res.status_int) - - def test_disablefile_disabled(self): - filename = self.create_tempfiles([('test', 'foobar')])[0] - conf = {'backends': 'disable_by_file', - 'disable_by_file_path': filename} - self._do_test(conf, - expected_code=webob.exc.HTTPServiceUnavailable.code, - expected_body=b'DISABLED BY FILE') - self.assertIn('disable_by_file', self.app._backends.names()) - - def test_disablefile_disabled_head(self): - filename = self.create_tempfiles([('test', 'foobar')])[0] - conf = {'backends': 'disable_by_file', - 'disable_by_file_path': filename} - self._do_test(conf, - expected_code=webob.exc.HTTPServiceUnavailable.code, - expected_body=b'', method='HEAD') - self.assertIn('disable_by_file', self.app._backends.names()) - - def test_disablefile_disabled_html_detailed(self): - filename = self.create_tempfiles([('test', 'foobar')])[0] - conf = {'backends': 'disable_by_file', - 'disable_by_file_path': filename, 'detailed': True} - res = self._do_test_request(conf, accept="text/html") - self.assertIn(b'DISABLED BY FILE', res.body) - self.assertEqual(webob.exc.HTTPServiceUnavailable.code, - res.status_int) - - def test_two_backends(self): - filename = self.create_tempfiles([('test', 'foobar')])[0] - conf = {'backends': 'disable_by_file,disable_by_file', - 'disable_by_file_path': filename} - self._do_test(conf, - expected_code=webob.exc.HTTPServiceUnavailable.code, - expected_body=b'DISABLED BY FILE\nDISABLED BY FILE') - self.assertIn('disable_by_file', self.app._backends.names()) - - def test_disable_by_port_file(self): - filename = self.create_tempfiles([('test', 'foobar')])[0] - conf = {'backends': 'disable_by_files_ports', - 'disable_by_file_paths': "80:%s" % filename} - self._do_test(conf, - expected_code=webob.exc.HTTPServiceUnavailable.code, - expected_body=b'DISABLED BY FILE') - self.assertIn('disable_by_files_ports', self.app._backends.names()) - - def test_no_disable_by_port_file(self): - filename = self.create_tempfiles([('test', 'foobar')])[0] - conf = {'backends': 'disable_by_files_ports', - 'disable_by_file_paths': "8000:%s" % filename} - self._do_test(conf, - expected_code=webob.exc.HTTPOk.code, - expected_body=b'OK') - self.assertIn('disable_by_files_ports', self.app._backends.names()) - - def test_disable_by_port_many_files(self): - filename = self.create_tempfiles([('test', 'foobar')])[0] - filename2 = self.create_tempfiles([('test2', 'foobar2')])[0] - conf = {'backends': 'disable_by_files_ports', - 'disable_by_file_paths': "80:%s,81:%s" % (filename, filename2)} - self._do_test(conf, - expected_code=webob.exc.HTTPServiceUnavailable.code, - expected_body=b'DISABLED BY FILE') - self._do_test(conf, - expected_code=webob.exc.HTTPServiceUnavailable.code, - expected_body=b'DISABLED BY FILE', - server_port=81) - self.assertIn('disable_by_files_ports', self.app._backends.names()) diff --git a/oslo_middleware/tests/test_http_proxy_to_wsgi.py b/oslo_middleware/tests/test_http_proxy_to_wsgi.py deleted file mode 100644 index 26baa77..0000000 --- a/oslo_middleware/tests/test_http_proxy_to_wsgi.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2015 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -from wsgiref import util - -from oslotest import base as test_base -import webob - -from oslo_middleware import http_proxy_to_wsgi - - -class TestHTTPProxyToWSGI(test_base.BaseTestCase): - - def setUp(self): - super(TestHTTPProxyToWSGI, self).setUp() - - @webob.dec.wsgify() - def fake_app(req): - return util.application_uri(req.environ) - - self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGI(fake_app) - self.middleware.oslo_conf.set_override('enable_proxy_headers_parsing', - True, - group='oslo_middleware', - enforce_type=True) - self.request = webob.Request.blank('/foo/bar', method='POST') - - def test_backward_compat(self): - @webob.dec.wsgify() - def fake_app(req): - return util.application_uri(req.environ) - - self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGIMiddleware( - fake_app) - response = self.request.get_response(self.middleware) - self.assertEqual(b"http://localhost:80/", response.body) - - def test_no_headers(self): - response = self.request.get_response(self.middleware) - self.assertEqual(b"http://localhost:80/", response.body) - - def test_url_translate_ssl(self): - self.request.headers['X-Forwarded-Proto'] = "https" - response = self.request.get_response(self.middleware) - self.assertEqual(b"https://localhost:80/", response.body) - - def test_url_translate_ssl_port(self): - self.request.headers['X-Forwarded-Proto'] = "https" - self.request.headers['X-Forwarded-Host'] = "example.com:123" - response = self.request.get_response(self.middleware) - self.assertEqual(b"https://example.com:123/", response.body) - - def test_url_translate_host_ipv6(self): - self.request.headers['X-Forwarded-Proto'] = "https" - self.request.headers['X-Forwarded-Host'] = "[f00:b4d::1]:123" - response = self.request.get_response(self.middleware) - self.assertEqual(b"https://[f00:b4d::1]:123/", response.body) - - def test_url_translate_base(self): - self.request.headers['X-Forwarded-Prefix'] = "/bla" - response = self.request.get_response(self.middleware) - self.assertEqual(b"http://localhost:80/bla", response.body) - - def test_url_translate_port_and_base_and_proto_and_host(self): - self.request.headers['X-Forwarded-Proto'] = "https" - self.request.headers['X-Forwarded-Prefix'] = "/bla" - self.request.headers['X-Forwarded-Host'] = "example.com:8043" - response = self.request.get_response(self.middleware) - self.assertEqual(b"https://example.com:8043/bla", response.body) - - def test_rfc7239_invalid(self): - self.request.headers['Forwarded'] = ( - "iam=anattacker;metoo, I will crash you!!P;m,xx") - response = self.request.get_response(self.middleware) - self.assertEqual(b"http://localhost:80/", response.body) - - def test_rfc7239_proto(self): - self.request.headers['Forwarded'] = ( - "for=foobar;proto=https, for=foobaz;proto=http") - response = self.request.get_response(self.middleware) - self.assertEqual(b"https://localhost:80/", response.body) - - def test_rfc7239_proto_host(self): - self.request.headers['Forwarded'] = ( - "for=foobar;proto=https;host=example.com, for=foobaz;proto=http") - response = self.request.get_response(self.middleware) - self.assertEqual(b"https://example.com/", response.body) - - def test_rfc7239_proto_host_base(self): - self.request.headers['Forwarded'] = ( - "for=foobar;proto=https;host=example.com:8043, for=foobaz") - self.request.headers['X-Forwarded-Prefix'] = "/bla" - response = self.request.get_response(self.middleware) - self.assertEqual(b"https://example.com:8043/bla", response.body) - - -class TestHTTPProxyToWSGIDisabled(test_base.BaseTestCase): - - def setUp(self): - super(TestHTTPProxyToWSGIDisabled, self).setUp() - - @webob.dec.wsgify() - def fake_app(req): - return util.application_uri(req.environ) - - self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGI(fake_app) - self.middleware.oslo_conf.set_override('enable_proxy_headers_parsing', - False, - group='oslo_middleware', - enforce_type=True) - self.request = webob.Request.blank('/foo/bar', method='POST') - - def test_no_headers(self): - response = self.request.get_response(self.middleware) - self.assertEqual(b"http://localhost:80/", response.body) - - def test_url_translate_ssl_has_no_effect(self): - self.request.headers['X-Forwarded-Proto'] = "https" - self.request.headers['X-Forwarded-Host'] = "example.com:123" - response = self.request.get_response(self.middleware) - self.assertEqual(b"http://localhost:80/", response.body) diff --git a/oslo_middleware/tests/test_opts.py b/oslo_middleware/tests/test_opts.py deleted file mode 100644 index 763d550..0000000 --- a/oslo_middleware/tests/test_opts.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. -# -# 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_middleware import opts -from oslotest.base import BaseTestCase - - -class TestOptionDiscovery(BaseTestCase): - - def test_all(self): - opts.list_opts() - - def test_sizelimit(self): - opts.list_opts_sizelimit() - - def test_cors(self): - opts.list_opts_cors() - - def test_ssl(self): - opts.list_opts_ssl() diff --git a/oslo_middleware/tests/test_request_id.py b/oslo_middleware/tests/test_request_id.py deleted file mode 100644 index 76f3696..0000000 --- a/oslo_middleware/tests/test_request_id.py +++ /dev/null @@ -1,39 +0,0 @@ -# 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) - if isinstance(res_req_id, bytes): - res_req_id = res_req_id.decode('utf-8') - self.assertThat(res_req_id, matchers.StartsWith('req-')) - # request-id in request environ is returned as response body - self.assertEqual(res_req_id, res.body.decode('utf-8')) diff --git a/oslo_middleware/tests/test_sizelimit.py b/oslo_middleware/tests/test_sizelimit.py deleted file mode 100644 index dc29cd0..0000000 --- a/oslo_middleware/tests/test_sizelimit.py +++ /dev/null @@ -1,108 +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. - -from oslo_config import fixture as config -from oslotest import base as test_base -import six -import webob - -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() - self.useFixture(config.Config()) - - @webob.dec.wsgify() - def fake_app(req): - return webob.Response(req.body) - - self.middleware = sizelimit.RequestBodySizeLimiter(fake_app) - self.MAX_REQUEST_BODY_SIZE = ( - self.middleware.oslo_conf.oslo_middleware.max_request_body_size) - 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/oslo_middleware/tests/test_ssl.py b/oslo_middleware/tests/test_ssl.py deleted file mode 100644 index e493a3a..0000000 --- a/oslo_middleware/tests/test_ssl.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (c) 2015 Thales Services SAS -# 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_config import fixture as config -from oslotest import base -import webob - -from oslo_middleware import ssl - - -class SSLMiddlewareTest(base.BaseTestCase): - - def setUp(self): - super(SSLMiddlewareTest, self).setUp() - self.useFixture(config.Config()) - - def _test_scheme(self, expected, headers, secure_proxy_ssl_header=None): - middleware = ssl.SSLMiddleware(None) - if secure_proxy_ssl_header: - middleware.oslo_conf.set_override( - 'secure_proxy_ssl_header', secure_proxy_ssl_header, - group='oslo_middleware', enforce_type=True) - request = webob.Request.blank('http://example.com/', headers=headers) - - # Ensure ssl middleware does not stop pipeline execution - self.assertIsNone(middleware.process_request(request)) - - self.assertEqual(expected, request.scheme) - - def test_without_forwarded_protocol(self): - self._test_scheme('http', {}) - - def test_with_forwarded_protocol(self): - headers = {'X-Forwarded-Proto': 'https'} - self._test_scheme('https', headers) - - def test_with_custom_header(self): - headers = {'X-Forwarded-Proto': 'https'} - self._test_scheme('http', headers, - secure_proxy_ssl_header='X-My-Header') - - def test_with_custom_header_and_forwarded_protocol(self): - headers = {'X-My-Header': 'https'} - self._test_scheme('https', headers, - secure_proxy_ssl_header='X-My-Header') diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0c6af5d..0000000 --- a/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# 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 -oslo.context>=2.4.0 # Apache-2.0 -oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.16.0 # Apache-2.0 -six>=1.9.0 # MIT -stevedore>=1.16.0 # Apache-2.0 -WebOb>=1.2.3 # MIT -debtcollector>=1.2.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index d2a7686..0000000 --- a/setup.cfg +++ /dev/null @@ -1,74 +0,0 @@ -[metadata] -name = oslo.middleware -summary = Oslo Middleware library -description-file = - README.rst -author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = http://wiki.openstack.org/wiki/Oslo#oslo.middleware -classifier = - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 - -[files] -packages = - oslo_middleware - -[entry_points] -oslo.config.opts = - oslo.middleware = oslo_middleware.opts:list_opts - oslo.middleware.cors = oslo_middleware.opts:list_opts_cors - 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 = - disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck - disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck - -paste.filter_factory = - catch_errors = oslo_middleware:CatchErrors.factory - correlation_id = oslo_middleware:CorrelationId.factory - cors = oslo_middleware:CORS.factory - debug = oslo_middleware:Debug.factory - healthcheck = oslo_middleware:Healthcheck.factory - http_proxy_to_wsgi = oslo_middleware:HTTPProxyToWSGI.factory - request_id = oslo_middleware:RequestId.factory - sizelimit = oslo_middleware:RequestBodySizeLimiter.factory - ssl = oslo_middleware:SSLMiddleware.factory - -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 - -[upload_sphinx] -upload-dir = doc/build/html - -[compile_catalog] -directory = oslo_middleware/locale -domain = oslo_middleware - -[update_catalog] -domain = oslo_middleware -output_dir = oslo_middleware/locale -input_file = oslo_middleware/locale/oslo_middleware.pot - -[extract_messages] -keywords = _ gettext ngettext l_ lazy_gettext -mapping_file = babel.cfg -output_file = oslo_middleware/locale/oslo_middleware.pot - -[pbr] -warnerrors = True - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 782bb21..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - -setuptools.setup( - setup_requires=['pbr>=1.8'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 5f53419..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -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 -oslotest>=1.10.0 # Apache-2.0 -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD -testtools>=1.4.0 # MIT -coverage>=3.6 # Apache-2.0 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 9ff7d03..0000000 --- a/tox.ini +++ /dev/null @@ -1,37 +0,0 @@ -[tox] -minversion = 1.6 -envlist = py34,py27,pypy,pep8 - -[testenv] -deps = -r{toxinidir}/test-requirements.txt -commands = python setup.py testr --slowest --testr-args='{posargs}' - -[testenv:pep8] -commands = flake8 - -[testenv:venv] -commands = {posargs} - -[testenv:docs] -commands = python setup.py build_sphinx - -[testenv:cover] -commands = python setup.py test --coverage --coverage-package-name=oslo_middleware --testr-args='{posargs}' - -[flake8] -# E123, E125 skipped as they are invalid PEP-8. - -show-source = True -ignore = E123,E125 -exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py - -[hacking] -import_exceptions = oslo_middleware._i18n - -[testenv:pip-missing-reqs] -# do not install test-requirements as that will pollute the virtualenv for -# determining missing packages -# this also means that pip-missing-reqs must be installed separately, outside -# 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