Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: Ia7cebaf44d32fa813527e77402079be1c85fe15c
This commit is contained in:
parent
1cf39ee5c3
commit
c979629a70
|
@ -1,8 +0,0 @@
|
|||
[run]
|
||||
branch = True
|
||||
source = oslo_middleware
|
||||
omit = oslo_middleware/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
precision = 2
|
|
@ -1,55 +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
|
||||
|
||||
# reno build
|
||||
releasenotes/build
|
|
@ -1,4 +0,0 @@
|
|||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/oslo.middleware.git
|
3
.mailmap
3
.mailmap
|
@ -1,3 +0,0 @@
|
|||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
|
@ -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
|
|
@ -1,16 +0,0 @@
|
|||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in this page:
|
||||
|
||||
https://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:
|
||||
|
||||
https://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
|
|
@ -1,4 +0,0 @@
|
|||
oslo.middleware Style Commandments
|
||||
==================================
|
||||
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
175
LICENSE
175
LICENSE
|
@ -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.
|
|
@ -0,0 +1,14 @@
|
|||
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".
|
||||
|
||||
For ongoing work on maintaining OpenStack packages in the Debian
|
||||
distribution, please see the Debian OpenStack packaging team at
|
||||
https://wiki.debian.org/OpenStack/.
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
21
README.rst
21
README.rst
|
@ -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: https://docs.openstack.org/oslo.middleware/latest
|
||||
* Source: https://git.openstack.org/cgit/openstack/oslo.middleware
|
||||
* Bugs: https://bugs.launchpad.net/oslo.middleware
|
|
@ -1,195 +0,0 @@
|
|||
.. _cross-project:
|
||||
|
||||
=============================
|
||||
Cross-origin resource sharing
|
||||
=============================
|
||||
|
||||
.. note::
|
||||
|
||||
This is a new feature in OpenStack Liberty.
|
||||
|
||||
OpenStack supports :term:`Cross-Origin Resource Sharing (CORS)`, a W3C
|
||||
specification defining a contract by which the single-origin policy of a user
|
||||
agent (usually a browser) may be relaxed. It permits the javascript engine
|
||||
to access an API that does not reside on the same domain, protocol, or port.
|
||||
|
||||
This feature is most useful to organizations which maintain one or more
|
||||
custom user interfaces for OpenStack, as it permits those interfaces to access
|
||||
the services directly, rather than requiring an intermediate proxy server. It
|
||||
can, however, also be misused by malicious actors; please review the
|
||||
security advisory below for more information.
|
||||
|
||||
Enabling CORS with configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In most cases, CORS support is built directly into the service itself. To
|
||||
enable it, simply follow the configuration options exposed in the default
|
||||
configuration file, or add it yourself according to the pattern below.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[cors]
|
||||
allowed_origin = https://first_ui.example.com
|
||||
max_age = 3600
|
||||
allow_methods = GET,POST,PUT,DELETE
|
||||
allow_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header
|
||||
expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header
|
||||
|
||||
Additional origins can be explicitly added. To express this in
|
||||
your configuration file, first begin with a ``[cors]`` group as above,
|
||||
into which you place your default configuration values. Then, add as many
|
||||
additional configuration groups as necessary, naming them
|
||||
``[cors.{something}]`` (each name must be unique). The purpose of the
|
||||
suffix to ``cors.`` is legibility, we recommend using a reasonable
|
||||
human-readable string:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[cors.ironic_webclient]
|
||||
# CORS Configuration for a hypothetical ironic webclient, which overrides
|
||||
# authentication
|
||||
allowed_origin = https://ironic.example.com:443
|
||||
allow_credentials = True
|
||||
|
||||
[cors.horizon]
|
||||
# CORS Configuration for horizon, which uses global options.
|
||||
allowed_origin = https://horizon.example.com:443
|
||||
|
||||
[cors.wildcard]
|
||||
# CORS Configuration for the CORS specified domain wildcard, which only
|
||||
# permits HTTP GET requests.
|
||||
allowed_origin = *
|
||||
allow_methods = GET
|
||||
|
||||
For more information about CORS configuration,
|
||||
see `cross-origin resource sharing
|
||||
<https://docs.openstack.org/ocata/config-reference/common-configurations/cors.html>`_
|
||||
in OpenStack Configuration Reference.
|
||||
|
||||
Enabling CORS with PasteDeploy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
CORS can also be configured using PasteDeploy. First of all, ensure that
|
||||
OpenStack's ``oslo_middleware`` package (version 2.4.0 or later) is
|
||||
available in the Python environment that is running the service. Then,
|
||||
add the following configuration block to your ``paste.ini`` file.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[filter:cors]
|
||||
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||
allowed_origin = https://website.example.com:443
|
||||
max_age = 3600
|
||||
allow_methods = GET,POST,PUT,DELETE
|
||||
allow_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header
|
||||
expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header
|
||||
|
||||
.. note::
|
||||
To add an additional domain in oslo_middleware v2.4.0, add
|
||||
another filter. In v3.0.0 and after, you may add multiple domains
|
||||
in the above ``allowed_origin`` field, separated by commas.
|
||||
|
||||
Security concerns
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
CORS specifies a wildcard character ``*``, which permits access to all user
|
||||
agents, regardless of domain, protocol, or host. While there are valid use
|
||||
cases for this approach, it also permits a malicious actor to create a
|
||||
convincing facsimile of a user interface, and trick users into revealing
|
||||
authentication credentials. Please carefully evaluate your use case and the
|
||||
relevant documentation for any risk to your organization.
|
||||
|
||||
.. note::
|
||||
|
||||
The CORS specification does not support using this wildcard as
|
||||
a part of a URI. Setting ``allowed_origin`` to ``*`` would work, while
|
||||
``*.openstack.org`` would not.
|
||||
|
||||
Troubleshooting
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
CORS is very easy to get wrong, as even one incorrect property will violate
|
||||
the prescribed contract. Here are some steps you can take to troubleshoot
|
||||
your configuration.
|
||||
|
||||
Check the service log
|
||||
---------------------
|
||||
|
||||
The CORS middleware used by OpenStack provides verbose debug logging that
|
||||
should reveal most configuration problems. Here are some example log
|
||||
messages, and how to resolve them.
|
||||
|
||||
Problem
|
||||
-------
|
||||
|
||||
``CORS request from origin 'http://example.com' not permitted.``
|
||||
|
||||
Solution
|
||||
--------
|
||||
|
||||
A request was received from the origin ``http://example.com``, however this
|
||||
origin was not found in the permitted list. The cause may be a superfluous
|
||||
port notation (ports 80 and 443 do not need to be specified). To correct,
|
||||
ensure that the configuration property for this host is identical to the
|
||||
host indicated in the log message.
|
||||
|
||||
Problem
|
||||
-------
|
||||
|
||||
``Request method 'DELETE' not in permitted list: GET,PUT,POST``
|
||||
|
||||
Solution
|
||||
--------
|
||||
|
||||
A user agent has requested permission to perform a DELETE request, however
|
||||
the CORS configuration for the domain does not permit this. To correct, add
|
||||
this method to the ``allow_methods`` configuration property.
|
||||
|
||||
Problem
|
||||
-------
|
||||
|
||||
``Request header 'X-Custom-Header' not in permitted list: X-Other-Header``
|
||||
|
||||
Solution
|
||||
--------
|
||||
|
||||
A request was received with the header ``X-Custom-Header``, which is not
|
||||
permitted. Add this header to the ``allow_headers`` configuration
|
||||
property.
|
||||
|
||||
Open your browser's console log
|
||||
-------------------------------
|
||||
|
||||
Most browsers provide helpful debug output when a CORS request is rejected.
|
||||
Usually this happens when a request was successful, but the return headers on
|
||||
the response do not permit access to a property which the browser is trying
|
||||
to access.
|
||||
|
||||
Manually construct a CORS request
|
||||
---------------------------------
|
||||
|
||||
By using ``curl`` or a similar tool, you can trigger a CORS response with a
|
||||
properly constructed HTTP request. An example request and response might look
|
||||
like this.
|
||||
|
||||
Request example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ curl -I -X OPTIONS https://api.example.com/api -H "Origin: https://ui.example.com"
|
||||
|
||||
Response example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Content-Length: 0
|
||||
Access-Control-Allow-Origin: https://ui.example.com
|
||||
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
|
||||
Access-Control-Expose-Headers: origin,authorization,accept,x-total,x-limit,x-marker,x-client,content-type
|
||||
Access-Control-Allow-Headers: origin,authorization,accept,x-total,x-limit,x-marker,x-client,content-type
|
||||
Access-Control-Max-Age: 3600
|
||||
|
||||
If the service does not return any access control headers, check the service
|
||||
log, such as ``/var/log/upstart/ironic-api.log`` for an indication on what
|
||||
went wrong.
|
|
@ -1,13 +0,0 @@
|
|||
======================
|
||||
Cross-project features
|
||||
======================
|
||||
|
||||
Many features are common to all the OpenStack services and are consistent in
|
||||
their configuration and deployment patterns. Unless explicitly noted, you can
|
||||
safely assume that the features in this chapter are supported and configured
|
||||
in a consistent manner.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
cross-project-cors.rst
|
|
@ -1,85 +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',
|
||||
'oslo_config.sphinxext',
|
||||
'openstackdocstheme',
|
||||
'stevedore.sphinxext',
|
||||
]
|
||||
|
||||
# openstackdocstheme options
|
||||
repository_name = 'openstack/oslo.middleware'
|
||||
bug_project = 'oslo.middleware'
|
||||
bug_tag = ''
|
||||
|
||||
# Must set this variable to include year, month, day, hours, and minutes.
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
# 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}
|
|
@ -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]
|
||||
use = egg:oslo.middleware#sizelimit
|
||||
# In case of the application doesn't use the global oslo.config
|
||||
# object. The middleware must known the app name to load
|
||||
# the application configuration, by setting this:
|
||||
# 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]
|
||||
use = egg:oslo.middleware#sizelimit
|
||||
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:`../reference/healthcheck_plugins`
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
==============
|
||||
Contributing
|
||||
==============
|
||||
|
||||
.. include:: ../../../CONTRIBUTING.rst
|
|
@ -1,19 +0,0 @@
|
|||
========
|
||||
Glossary
|
||||
========
|
||||
|
||||
This glossary offers a list of terms and definitions to define a
|
||||
vocabulary for OpenStack-related concepts.
|
||||
|
||||
C
|
||||
~
|
||||
|
||||
.. glossary::
|
||||
|
||||
Cross-Origin Resource Sharing (CORS)
|
||||
|
||||
A mechanism that allows many resources (for example,
|
||||
fonts, JavaScript) on a web page to be requested from
|
||||
another domain outside the domain from which the resource
|
||||
originated. In particular, JavaScript's AJAX calls can use
|
||||
the XMLHttpRequest mechanism.
|
|
@ -1,22 +0,0 @@
|
|||
.. include:: ../../README.rst
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install/index
|
||||
contributor/index
|
||||
configuration/index
|
||||
admin/index
|
||||
reference/index
|
||||
glossary
|
||||
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
user/history
|
|
@ -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
|
|
@ -1,19 +0,0 @@
|
|||
=====
|
||||
API
|
||||
=====
|
||||
|
||||
.. automodule:: oslo_middleware
|
||||
:members:
|
||||
|
||||
Configuration Options
|
||||
=====================
|
||||
|
||||
RequestBodySizeLimiter
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. show-options:: oslo.middleware.sizelimit
|
||||
|
||||
SSLMiddleware
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. show-options:: oslo.middleware.ssl
|
|
@ -1,94 +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
|
||||
|
||||
Configuration for pastedeploy
|
||||
-----------------------------
|
||||
|
||||
If your application is using pastedeploy, the following configuration block
|
||||
will add CORS support.::
|
||||
|
||||
[filter:cors]
|
||||
use = egg:oslo.middleware#cors
|
||||
allowed_origin=https://website.example.com:443,https://website2.example.com:443
|
||||
max_age=3600
|
||||
allow_methods=GET,POST,PUT,DELETE
|
||||
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]
|
||||
use = egg:oslo.middleware#cors
|
||||
oslo_config_project = oslo_project_name
|
||||
|
||||
# Optional field, in case the program name is different from the project:
|
||||
oslo_config_program = oslo_project_name-api
|
||||
|
||||
Configuration Options
|
||||
---------------------
|
||||
|
||||
.. show-options:: oslo.middleware.cors
|
||||
|
||||
Module Documentation
|
||||
--------------------
|
||||
|
||||
.. automodule:: oslo_middleware.cors
|
||||
:members:
|
||||
|
||||
.. _CORS: http://www.w3.org/TR/cors/
|
|
@ -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:
|
|
@ -1,8 +0,0 @@
|
|||
==============================
|
||||
oslo.middleware Reference
|
||||
==============================
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
*
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../../ChangeLog
|
|
@ -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__)
|
|
@ -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
|
|
@ -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
|
|
@ -1,25 +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 https://docs.openstack.org/oslo.i18n/latest/user/index.html
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='oslo_middleware')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
|
@ -1,149 +0,0 @@
|
|||
# Copyright 2011 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Base class(es) for WSGI Middleware."""
|
||||
|
||||
import six
|
||||
|
||||
if six.PY2:
|
||||
from inspect import getargspec as getfullargspec
|
||||
else:
|
||||
from inspect import getfullargspec
|
||||
|
||||
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)
|
||||
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 = getfullargspec(self.process_response)[0]
|
||||
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
|
|
@ -1,46 +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 re
|
||||
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from oslo_middleware import base
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_TOKEN_RE = re.compile('^(X-\w+-Token):.*$', flags=re.MULTILINE)
|
||||
|
||||
|
||||
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:
|
||||
req_str = _TOKEN_RE.sub(r'\1: *****', req.as_text())
|
||||
LOG.exception('An error occurred during '
|
||||
'processing the request: %s', req_str)
|
||||
response = webob.exc.HTTPInternalServerError()
|
||||
return response
|
|
@ -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.
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
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
|
||||
uuidutils.generate_uuid())
|
||||
req.headers['X_CORRELATION_ID'] = correlation_id
|
|
@ -1,455 +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
|
||||
from debtcollector import moves
|
||||
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',
|
||||
help='Indicate whether this resource may be shared with the '
|
||||
'domain received in the requests "origin" header. '
|
||||
'Format: "<protocol>://<host>[:<port>]", 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
|
||||
}
|
||||
|
||||
@moves.moved_method('set_defaults',
|
||||
message='CORS.set_latent has been deprecated in favor '
|
||||
'of oslo_middleware.cors.set_defaults')
|
||||
def set_latent(self, allow_headers=None, allow_methods=None,
|
||||
expose_headers=None):
|
||||
'''Add a new latent property for this middleware.
|
||||
|
||||
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
|
|
@ -1,59 +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 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 resp.headers.items():
|
||||
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()
|
|
@ -1,561 +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
|
||||
|
||||
from debtcollector import removals
|
||||
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
|
||||
from oslo_middleware.healthcheck import opts
|
||||
|
||||
|
||||
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 application used for monitoring.
|
||||
|
||||
It will respond 200 with "OK" as the body. Or a 503 with the reason as the
|
||||
body if one of the backends reports an application issue.
|
||||
|
||||
This is useful for the following reasons:
|
||||
|
||||
* Load balancers can 'ping' this url to determine service availability.
|
||||
* 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.
|
||||
* *(and more)*
|
||||
|
||||
Example requests/responses (**not** detailed mode)::
|
||||
|
||||
$ curl -i -X HEAD "http://0.0.0.0:8775/healthcheck"
|
||||
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 -X GET "http://0.0.0.0:8775/healthcheck"
|
||||
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
|
||||
|
||||
$ curl -X GET -i -H "Accept: application/json" "http://0.0.0.0:8775/healthcheck"
|
||||
HTTP/1.0 200 OK
|
||||
Date: Wed, 24 Aug 2016 06:09:58 GMT
|
||||
Content-Type: application/json
|
||||
Content-Length: 63
|
||||
|
||||
{
|
||||
"detailed": false,
|
||||
"reasons": [
|
||||
"OK"
|
||||
]
|
||||
}
|
||||
|
||||
$ curl -X GET -i -H "Accept: text/html" "http://0.0.0.0:8775/healthcheck"
|
||||
HTTP/1.0 200 OK
|
||||
Date: Wed, 24 Aug 2016 06:10:42 GMT
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Length: 239
|
||||
|
||||
<HTML>
|
||||
<HEAD><TITLE>Healthcheck Status</TITLE></HEAD>
|
||||
<BODY>
|
||||
|
||||
<H2>Result of 1 checks:</H2>
|
||||
<TABLE bgcolor="#ffffff" border="1">
|
||||
<TBODY>
|
||||
<TR>
|
||||
|
||||
<TH>
|
||||
Reason
|
||||
</TH>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>OK</TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
<HR></HR>
|
||||
|
||||
</BODY>
|
||||
|
||||
Example requests/responses (**detailed** mode)::
|
||||
|
||||
$ curl -X GET -i -H "Accept: application/json" "http://0.0.0.0:8775/healthcheck"
|
||||
HTTP/1.0 200 OK
|
||||
Date: Wed, 24 Aug 2016 06:11:59 GMT
|
||||
Content-Type: application/json
|
||||
Content-Length: 3480
|
||||
|
||||
{
|
||||
"detailed": true,
|
||||
"gc": {
|
||||
"counts": [
|
||||
293,
|
||||
10,
|
||||
5
|
||||
],
|
||||
"threshold": [
|
||||
700,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"greenthreads": [
|
||||
...
|
||||
],
|
||||
"now": "2016-08-24 06:11:59.419267",
|
||||
"platform": "Linux-4.2.0-27-generic-x86_64-with-Ubuntu-14.04-trusty",
|
||||
"python_version": "2.7.6 (default, Jun 22 2015, 17:58:13) \\n[GCC 4.8.2]",
|
||||
"reasons": [
|
||||
{
|
||||
"class": "HealthcheckResult",
|
||||
"details": "Path '/tmp/dead' was not found",
|
||||
"reason": "OK"
|
||||
}
|
||||
],
|
||||
"threads": [
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
$ curl -X GET -i -H "Accept: text/html" "http://0.0.0.0:8775/healthcheck"
|
||||
HTTP/1.0 200 OK
|
||||
Date: Wed, 24 Aug 2016 06:36:07 GMT
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Length: 6838
|
||||
|
||||
<HTML>
|
||||
<HEAD><TITLE>Healthcheck Status</TITLE></HEAD>
|
||||
<BODY>
|
||||
<H1>Server status</H1>
|
||||
<B>Server hostname:</B><PRE>...</PRE>
|
||||
<B>Current time:</B><PRE>2016-08-24 06:36:07.302559</PRE>
|
||||
<B>Python version:</B><PRE>2.7.6 (default, Jun 22 2015, 17:58:13)
|
||||
[GCC 4.8.2]</PRE>
|
||||
<B>Platform:</B><PRE>Linux-4.2.0-27-generic-x86_64-with-Ubuntu-14.04-trusty</PRE>
|
||||
<HR></HR>
|
||||
<H2>Garbage collector:</H2>
|
||||
<B>Counts:</B><PRE>(77, 1, 6)</PRE>
|
||||
<B>Thresholds:</B><PRE>(700, 10, 10)</PRE>
|
||||
|
||||
<HR></HR>
|
||||
<H2>Result of 1 checks:</H2>
|
||||
<TABLE bgcolor="#ffffff" border="1">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TH>
|
||||
Kind
|
||||
</TH>
|
||||
<TH>
|
||||
Reason
|
||||
</TH>
|
||||
<TH>
|
||||
Details
|
||||
</TH>
|
||||
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>HealthcheckResult</TD>
|
||||
<TD>OK</TD>
|
||||
<TD>Path '/tmp/dead' was not found</TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
<HR></HR>
|
||||
<H2>1 greenthread(s) active:</H2>
|
||||
<TABLE bgcolor="#ffffff" border="1">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD><PRE> File "oslo_middleware/healthcheck/__main__.py", line 94, in <module>
|
||||
main()
|
||||
File "oslo_middleware/healthcheck/__main__.py", line 90, in main
|
||||
server.serve_forever()
|
||||
...
|
||||
</PRE></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
<HR></HR>
|
||||
<H2>1 thread(s) active:</H2>
|
||||
<TABLE bgcolor="#ffffff" border="1">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD><PRE> File "oslo_middleware/healthcheck/__main__.py", line 94, in <module>
|
||||
main()
|
||||
File "oslo_middleware/healthcheck/__main__.py", line 90, in main
|
||||
server.serve_forever()
|
||||
....
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
||||
Example of paste configuration:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[app:healthcheck]
|
||||
use = egg:oslo.middleware: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
|
||||
|
||||
[composite:public_api]
|
||||
use = egg:Paste#urlmap
|
||||
/ = public_api_pipeline
|
||||
/healthcheck = healthcheck_public
|
||||
|
||||
[composite:admin_api]
|
||||
use = egg:Paste#urlmap
|
||||
/ = admin_api_pipeline
|
||||
/healthcheck = healthcheck_admin
|
||||
|
||||
[pipeline:public_api_pipeline]
|
||||
pipeline = sizelimit [...] public_service
|
||||
|
||||
[pipeline:admin_api_pipeline]
|
||||
pipeline = sizelimit [...] admin_service
|
||||
|
||||
[app:healthcheck_public]
|
||||
use = egg:oslo.middleware:healthcheck
|
||||
backends = disable_by_file
|
||||
disable_by_file_path = /var/run/nova/healthcheck_public_disable
|
||||
|
||||
[filter:healthcheck_admin]
|
||||
use = egg:oslo.middleware:healthcheck
|
||||
backends = disable_by_file
|
||||
disable_by_file_path = /var/run/nova/healthcheck_admin_disable
|
||||
"""
|
||||
|
||||
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 = """
|
||||
<HTML>
|
||||
<HEAD><TITLE>Healthcheck Status</TITLE></HEAD>
|
||||
<BODY>
|
||||
{% if detailed -%}
|
||||
<H1>Server status</H1>
|
||||
{% if hostname -%}
|
||||
<B>Server hostname:</B><PRE>{{hostname|e}}</PRE>
|
||||
{%- endif %}
|
||||
<B>Current time:</B><PRE>{{now|e}}</PRE>
|
||||
<B>Python version:</B><PRE>{{python_version|e}}</PRE>
|
||||
<B>Platform:</B><PRE>{{platform|e}}</PRE>
|
||||
<HR></HR>
|
||||
<H2>Garbage collector:</H2>
|
||||
<B>Counts:</B><PRE>{{gc.counts|e}}</PRE>
|
||||
<B>Thresholds:</B><PRE>{{gc.threshold|e}}</PRE>
|
||||
<HR></HR>
|
||||
{%- endif %}
|
||||
<H2>Result of {{results|length}} checks:</H2>
|
||||
<TABLE bgcolor="#ffffff" border="1">
|
||||
<TBODY>
|
||||
<TR>
|
||||
{% if detailed -%}
|
||||
<TH>
|
||||
Kind
|
||||
</TH>
|
||||
<TH>
|
||||
Reason
|
||||
</TH>
|
||||
<TH>
|
||||
Details
|
||||
</TH>
|
||||
{% else %}
|
||||
<TH>
|
||||
Reason
|
||||
</TH>
|
||||
{%- endif %}
|
||||
</TR>
|
||||
{% for result in results -%}
|
||||
{% if result.reason -%}
|
||||
<TR>
|
||||
{% if detailed -%}
|
||||
<TD>{{result.class|e}}</TD>
|
||||
{%- endif %}
|
||||
<TD>{{result.reason|e}}</TD>
|
||||
{% if detailed -%}
|
||||
<TD>{{result.details|e}}</TD>
|
||||
{%- endif %}
|
||||
</TR>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
<HR></HR>
|
||||
{% if detailed -%}
|
||||
{% if greenthreads -%}
|
||||
<H2>{{greenthreads|length}} greenthread(s) active:</H2>
|
||||
<TABLE bgcolor="#ffffff" border="1">
|
||||
<TBODY>
|
||||
{% for stack in greenthreads -%}
|
||||
<TR>
|
||||
<TD><PRE>{{stack|e}}</PRE></TD>
|
||||
</TR>
|
||||
{%- endfor %}
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
<HR></HR>
|
||||
{%- endif %}
|
||||
{% if threads -%}
|
||||
<H2>{{threads|length}} thread(s) active:</H2>
|
||||
<TABLE bgcolor="#ffffff" border="1">
|
||||
<TBODY>
|
||||
{% for stack in threads -%}
|
||||
<TR>
|
||||
<TD><PRE>{{stack|e}}</PRE></TD>
|
||||
</TR>
|
||||
{%- endfor %}
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
</BODY>
|
||||
</HTML>
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Healthcheck, self).__init__(*args, **kwargs)
|
||||
self.oslo_conf.register_opts(opts.HEALTHCHECK_OPTS,
|
||||
group='healthcheck')
|
||||
self._path = self._conf_get('path')
|
||||
self._show_details = self._conf_get('detailed')
|
||||
self._backends = stevedore.NamedExtensionManager(
|
||||
self.NAMESPACE, self._conf_get('backends'),
|
||||
name_order=True, invoke_on_load=True,
|
||||
invoke_args=(self.oslo_conf, self.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'
|
||||
self._ignore_path = False
|
||||
|
||||
def _conf_get(self, key, group='healthcheck'):
|
||||
return super(Healthcheck, self)._conf_get(key, group=group)
|
||||
|
||||
@removals.remove(
|
||||
message="The healthcheck middleware must now be configured as "
|
||||
"an application, not as a filter")
|
||||
@classmethod
|
||||
def factory(cls, global_conf, **local_conf):
|
||||
return super(Healthcheck, cls).factory(global_conf, **local_conf)
|
||||
|
||||
@classmethod
|
||||
def app_factory(cls, global_conf, **local_conf):
|
||||
"""Factory method for paste.deploy.
|
||||
|
||||
:param global_conf: dict of options for all middlewares
|
||||
(usually the [DEFAULT] section of the paste deploy
|
||||
configuration file)
|
||||
:param local_conf: options dedicated to this middleware
|
||||
(usually the option defined in the middleware
|
||||
section of the paste deploy configuration file)
|
||||
"""
|
||||
conf = global_conf.copy() if global_conf else {}
|
||||
conf.update(local_conf)
|
||||
o = cls(application=None, conf=conf)
|
||||
o._ignore_path = True
|
||||
return o
|
||||
|
||||
@staticmethod
|
||||
def _get_threadstacks():
|
||||
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 not self._ignore_path and 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)
|
|
@ -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()
|
|
@ -1,122 +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.healthcheck import opts
|
||||
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
|
||||
# set to True to enable detailed output, False is the default
|
||||
detailed = False
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DisableByFilesPortsHealthcheck, self).__init__(*args, **kwargs)
|
||||
self.oslo_conf.register_opts(opts.DISABLE_BY_FILES_OPTS,
|
||||
group='healthcheck')
|
||||
self.status_files = {}
|
||||
paths = self._conf_get('disable_by_file_paths')
|
||||
self.status_files.update(self._iter_paths_ports(paths))
|
||||
|
||||
@staticmethod
|
||||
def _iter_paths_ports(paths):
|
||||
for port_path in paths:
|
||||
port_path = port_path.strip()
|
||||
if port_path:
|
||||
# On windows, drive letters are followed by colons,
|
||||
# which makes split() return 3 elements in this case
|
||||
port, path = port_path.split(":", 1)
|
||||
port = int(port)
|
||||
yield (port, path)
|
||||
|
||||
def healthcheck(self, server_port):
|
||||
path = self.status_files.get(server_port)
|
||||
if not path:
|
||||
LOG.warning('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
|
||||
# set to True to enable detailed output, False is the default
|
||||
detailed = False
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DisableByFileHealthcheck, self).__init__(*args, **kwargs)
|
||||
self.oslo_conf.register_opts(opts.DISABLE_BY_FILE_OPTS,
|
||||
group='healthcheck')
|
||||
|
||||
def healthcheck(self, server_port):
|
||||
path = self._conf_get('disable_by_file_path')
|
||||
if not path:
|
||||
LOG.warning('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)
|
|
@ -1,47 +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 oslo_config import cfg
|
||||
|
||||
|
||||
HEALTHCHECK_OPTS = [
|
||||
cfg.StrOpt('path',
|
||||
default='/healthcheck',
|
||||
deprecated_for_removal=True,
|
||||
help='The path to respond to healtcheck requests on.'),
|
||||
cfg.BoolOpt('detailed',
|
||||
default=False,
|
||||
help='Show more detailed information as part of the response'),
|
||||
cfg.ListOpt('backends',
|
||||
default=[],
|
||||
help='Additional backends that can perform health checks and '
|
||||
'report that information back as part of a request.'),
|
||||
]
|
||||
|
||||
|
||||
DISABLE_BY_FILE_OPTS = [
|
||||
cfg.StrOpt('disable_by_file_path',
|
||||
default=None,
|
||||
help='Check the presence of a file to determine if an '
|
||||
'application is running on a port. Used by '
|
||||
'DisableByFileHealthcheck plugin.'),
|
||||
]
|
||||
|
||||
|
||||
DISABLE_BY_FILES_OPTS = [
|
||||
cfg.ListOpt('disable_by_file_paths',
|
||||
default=[],
|
||||
help='Check the presence of a file based on a port to '
|
||||
'determine if an application is running on a port. '
|
||||
'Expects a "port:path" list of strings. Used by '
|
||||
'DisableByFilesPortsHealthcheck plugin.'),
|
||||
]
|
|
@ -1,48 +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, oslo_conf, conf):
|
||||
self.oslo_conf = oslo_conf
|
||||
self.conf = conf
|
||||
|
||||
@abc.abstractmethod
|
||||
def healthcheck(self, server_port):
|
||||
"""method called by the healthcheck middleware
|
||||
|
||||
return: HealthcheckResult object
|
||||
"""
|
||||
|
||||
def _conf_get(self, key, group='healthcheck'):
|
||||
if key in self.conf:
|
||||
# Validate value type
|
||||
self.oslo_conf.set_override(key, self.conf[key], group=group)
|
||||
return getattr(getattr(self.oslo_conf, group), key)
|
|
@ -1,99 +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
|
||||
|
||||
forwarded_for = proxy.get("for")
|
||||
if forwarded_for:
|
||||
req.environ['REMOTE_ADDR'] = forwarded_for
|
||||
|
||||
else:
|
||||
# World before RFC7239
|
||||
forwarded_proto = req.environ.get("HTTP_X_FORWARDED_PROTO")
|
||||
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
|
||||
|
||||
forwarded_for = req.environ.get("HTTP_X_FORWARDED_FOR")
|
||||
if forwarded_for:
|
||||
req.environ['REMOTE_ADDR'] = forwarded_for
|
||||
|
||||
v = req.environ.get("HTTP_X_FORWARDED_PREFIX")
|
||||
if v:
|
||||
req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME']
|
||||
|
||||
|
||||
@removals.remove
|
||||
class HTTPProxyToWSGIMiddleware(HTTPProxyToWSGI):
|
||||
"""Placeholder for backward compatibility"""
|
|
@ -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 <jaegerandi@gmail.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <jaegerandi@gmail.com>\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"
|
|
@ -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 <jaegerandi@gmail.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <jaegerandi@gmail.com>\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ß."
|
|
@ -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 <andi@gowling.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <andi@gowling.com>\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"
|
|
@ -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 <andi@gowling.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <andi@gowling.com>\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."
|
|
@ -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 <max.coquerel@gmail.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <max.coquerel@gmail.com>\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"
|
|
@ -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 <max.coquerel@gmail.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <max.coquerel@gmail.com>\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."
|
|
@ -1,187 +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',
|
||||
'list_opts_healthcheck',
|
||||
]
|
||||
|
||||
|
||||
import copy
|
||||
import itertools
|
||||
|
||||
from oslo_middleware import cors
|
||||
from oslo_middleware.healthcheck import opts as healthcheck_opts
|
||||
from oslo_middleware import http_proxy_to_wsgi
|
||||
from oslo_middleware import sizelimit
|
||||
from oslo_middleware import ssl
|
||||
|
||||
|
||||
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(),
|
||||
list_opts_healthcheck(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
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)),
|
||||
]
|
||||
|
||||
|
||||
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)),
|
||||
]
|
||||
|
||||
|
||||
def list_opts_healthcheck():
|
||||
"""Return a list of oslo.config options for healthcheck.
|
||||
|
||||
The returned list includes all oslo.config options which may be registered
|
||||
at runtime by the library.
|
||||
|
||||
Each element of the list is a tuple. The first element is the name of the
|
||||
group under which the list of elements in the second element will be
|
||||
registered. A group name of None corresponds to the [DEFAULT] group in
|
||||
config files.
|
||||
|
||||
This function is also discoverable via the 'oslo.middleware' entry point
|
||||
under the 'oslo.config.opts' namespace.
|
||||
|
||||
The purpose of this is to allow tools like the Oslo sample config file
|
||||
generator to discover the options exposed to users by this library.
|
||||
|
||||
:returns: a list of (group_name, opts) tuples
|
||||
"""
|
||||
# standard opts and the most common plugin to turn up in sample config.
|
||||
# can figure out a better way of exposing plugin opts later if required.
|
||||
return [
|
||||
('healthcheck', copy.deepcopy(healthcheck_opts.HEALTHCHECK_OPTS +
|
||||
healthcheck_opts.DISABLE_BY_FILE_OPTS +
|
||||
healthcheck_opts.DISABLE_BY_FILES_OPTS))
|
||||
]
|
|
@ -1,66 +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 re
|
||||
|
||||
from oslo_context import context
|
||||
import webob.dec
|
||||
|
||||
from oslo_middleware import base
|
||||
|
||||
|
||||
ENV_REQUEST_ID = 'openstack.request_id'
|
||||
GLOBAL_REQ_ID = 'openstack.global_request_id'
|
||||
HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id'
|
||||
INBOUND_HEADER = 'X-Openstack-Request-Id'
|
||||
ID_FORMAT = (r'^req-[a-f0-9]{8}-[a-f0-9]{4}-'
|
||||
r'[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$')
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
# if compat_headers is set, we also return the request_id in those
|
||||
# headers as well. This allows projects like Nova to adopt
|
||||
# oslo.middleware without impacting existing users.
|
||||
compat_headers = []
|
||||
|
||||
def set_global_req_id(self, req):
|
||||
gr_id = req.headers.get(INBOUND_HEADER, "")
|
||||
if re.match(ID_FORMAT, gr_id):
|
||||
req.environ[GLOBAL_REQ_ID] = gr_id
|
||||
# TODO(sdague): it would be nice to warn if we dropped a bogus
|
||||
# request_id, but the infrastructure for doing that isn't yet
|
||||
# setup at this stage.
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
self.set_global_req_id(req)
|
||||
|
||||
req_id = context.generate_request_id()
|
||||
req.environ[ENV_REQUEST_ID] = req_id
|
||||
response = req.get_response(self.application)
|
||||
|
||||
return_headers = [HTTP_RESP_HEADER_REQUEST_ID]
|
||||
return_headers.extend(self.compat_headers)
|
||||
|
||||
for header in return_headers:
|
||||
if header not in response.headers:
|
||||
response.headers.add(header, req_id)
|
||||
return response
|
|
@ -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:
|
||||
limiter = LimitingReader(req.body_file, max_size)
|
||||
req.body_file = limiter
|
||||
return self.application
|
|
@ -1,45 +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 a SSL termination proxy.")
|
||||
]
|
||||
|
||||
|
||||
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):
|
||||
removals.removed_module(__name__, "oslo_middleware.http_proxy_to_wsgi")
|
||||
super(SSLMiddleware, self).__init__(application, *args, **kwargs)
|
||||
self.oslo_conf.register_opts(OPTS, group='oslo_middleware')
|
||||
|
||||
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'])
|
|
@ -1,131 +0,0 @@
|
|||
# Copyright (c) 2016 Cisco Systems
|
||||
#
|
||||
# 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 re
|
||||
|
||||
import statsd
|
||||
import webob.dec
|
||||
|
||||
from oslo_middleware import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
VERSION_REGEX = re.compile("/(v[0-9]{1}\.[0-9]{1})")
|
||||
UUID_REGEX = re.compile(
|
||||
'.*(\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*a',
|
||||
re.IGNORECASE)
|
||||
# UUIDs without the - char, used in some places in Nova URLs.
|
||||
SHORT_UUID_REGEX = re.compile('.*(\.[0-9a-fA-F]{32}).*')
|
||||
|
||||
|
||||
class StatsMiddleware(base.ConfigurableMiddleware):
|
||||
"""Send stats to statsd based on API requests.
|
||||
|
||||
Examines the URL path and request method, and sends a stat count and timer
|
||||
to a statsd host based on the path/method.
|
||||
|
||||
If your statsd is configured to send stats to Graphite, you'll end up with
|
||||
stat names of the form::
|
||||
|
||||
timer.<appname>.<METHOD>.<path>.<from>.<url>
|
||||
|
||||
Note that URLs with versions in them (pretty much all of Openstack)
|
||||
are always processed to replace the dot with _, so for example v2.0
|
||||
becomes v2_0, and v1.1 becomes v1_1, since a dot '.' has special
|
||||
meaning in Graphite.
|
||||
|
||||
The original StatsD is written in nodejs. If you want a Python
|
||||
implementation, install Bucky instead as it's a drop-in replacement
|
||||
(and much nicer IMO).
|
||||
|
||||
The Paste config must contain some parameters. Configure a filter like
|
||||
this::
|
||||
|
||||
[filter:stats]
|
||||
paste.filter_factory = oslo_middleware.stats:StatsMiddleware.factory
|
||||
name = my_application_name # e.g. 'glance'
|
||||
stats_host = my_statsd_host.example.com
|
||||
# Optional args to further process the stat name that's generated:
|
||||
remove_uuid = True
|
||||
remove_short_uuid = True
|
||||
# The above uuid processing is required in, e.g. Nova, if you want to
|
||||
# collect generic stats rather than one per server instance.
|
||||
"""
|
||||
|
||||
def __init__(self, application, conf):
|
||||
super(StatsMiddleware, self).__init__(application, conf)
|
||||
self.application = application
|
||||
self.stat_name = conf.get('name')
|
||||
if self.stat_name is None:
|
||||
raise AttributeError('name must be specified')
|
||||
self.stats_host = conf.get('stats_host')
|
||||
if self.stats_host is None:
|
||||
raise AttributeError('stats_host must be specified')
|
||||
self.remove_uuid = conf.get('remove_uuid', False)
|
||||
self.remove_short_uuid = conf.get('remove_short_uuid', False)
|
||||
self.statsd = statsd.StatsClient(self.stats_host)
|
||||
|
||||
@staticmethod
|
||||
def strip_short_uuid(path):
|
||||
"""Remove short-form UUID from supplied path.
|
||||
|
||||
Only call after replacing slashes with dots in path.
|
||||
"""
|
||||
match = SHORT_UUID_REGEX.match(path)
|
||||
if match is None:
|
||||
return path
|
||||
return path.replace(match.group(1), '')
|
||||
|
||||
@staticmethod
|
||||
def strip_uuid(path):
|
||||
"""Remove normal-form UUID from supplied path.
|
||||
|
||||
Only call after replacing slashes with dots in path.
|
||||
"""
|
||||
match = UUID_REGEX.match(path)
|
||||
if match is None:
|
||||
return path
|
||||
return path.replace(match.group(1), '')
|
||||
|
||||
@staticmethod
|
||||
def strip_dot_from_version(path):
|
||||
# Replace vN.N with vNN.
|
||||
match = VERSION_REGEX.match(path)
|
||||
if match is None:
|
||||
return path
|
||||
return path.replace(match.group(1), match.group(1).replace('.', ''))
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, request):
|
||||
path = request.path
|
||||
path = self.strip_dot_from_version(path)
|
||||
|
||||
# Remove leading slash, if any, so we can be sure of the number
|
||||
# of dots just below.
|
||||
path = path.lstrip('/')
|
||||
|
||||
stat = "{name}.{method}".format(
|
||||
name=self.stat_name, method=request.method)
|
||||
if path != '':
|
||||
stat += '.' + path.replace('/', '.')
|
||||
|
||||
if self.remove_short_uuid:
|
||||
stat = self.strip_short_uuid(stat)
|
||||
|
||||
if self.remove_uuid:
|
||||
stat = self.strip_uuid(stat)
|
||||
|
||||
LOG.debug("Incrementing stat count %s", stat)
|
||||
with self.statsd.timer(stat):
|
||||
return request.get_response(self.application)
|
|
@ -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({'global': True, 'local': True}, app.conf)
|
||||
|
||||
|
||||
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
|
|
@ -1,75 +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 fixtures
|
||||
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')
|
||||
req.environ['HTTP_X_AUTH_TOKEN'] = 'hello=world'
|
||||
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)
|
||||
req_log = log_exc.call_args[0][1]
|
||||
self.assertIn('X-Auth-Token: *****', str(req_log))
|
||||
|
||||
def test_filter_tokens_from_log(self):
|
||||
logger = self.useFixture(fixtures.FakeLogger(nuke_handlers=False))
|
||||
|
||||
@webob.dec.wsgify
|
||||
def application(req):
|
||||
raise Exception()
|
||||
|
||||
app = catch_errors.CatchErrors(application)
|
||||
req = webob.Request.blank('/test',
|
||||
text=u'test data',
|
||||
method='POST',
|
||||
headers={'X-Auth-Token': 'secret1',
|
||||
'X-Service-Token': 'secret2',
|
||||
'X-Other-Token': 'secret3'})
|
||||
res = req.get_response(app)
|
||||
self.assertEqual(500, res.status_int)
|
||||
|
||||
output = logger.output
|
||||
|
||||
self.assertIn('X-Auth-Token: *****', output)
|
||||
self.assertIn('X-Service-Token: *****', output)
|
||||
self.assertIn('X-Other-Token: *****', output)
|
||||
self.assertIn('test data', output)
|
|
@ -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("fake_uuid", req.headers.get("X_CORRELATION_ID"))
|
||||
|
||||
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("correlation_id", req.headers.get("X_CORRELATION_ID"))
|
File diff suppressed because it is too large
Load Diff
|
@ -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))
|
|
@ -1,194 +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 oslo_config import fixture as config
|
||||
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):
|
||||
|
||||
def setUp(self):
|
||||
super(HealthcheckTests, self).setUp()
|
||||
self.useFixture(config.Config())
|
||||
|
||||
@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'<TD>OK</TD>', 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'<TD>DISABLED BY FILE</TD>', 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())
|
|
@ -1,152 +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')
|
||||
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)
|
||||
|
||||
def test_forwarded_for_headers(self):
|
||||
@webob.dec.wsgify()
|
||||
def fake_app(req):
|
||||
return req.environ['REMOTE_ADDR']
|
||||
|
||||
self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGIMiddleware(
|
||||
fake_app)
|
||||
forwarded_for_addr = '1.2.3.4'
|
||||
forwarded_addr = '8.8.8.8'
|
||||
|
||||
# If both X-Forwarded-For and Fowarded headers are present, it should
|
||||
# use the Forwarded header and ignore the X-Forwarded-For header.
|
||||
self.request.headers['Forwarded'] = (
|
||||
"for=%s;proto=https;host=example.com:8043" % (forwarded_addr))
|
||||
self.request.headers['X-Forwarded-For'] = forwarded_for_addr
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(forwarded_addr.encode(), response.body)
|
||||
|
||||
# Now if only X-Forwarded-For header is present, it should be used.
|
||||
del self.request.headers['Forwarded']
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(forwarded_for_addr.encode(), response.body)
|
||||
|
||||
|
||||
class TestHTTPProxyToWSGIDisabled(test_base.BaseTestCase):
|
||||
|
||||
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')
|
||||
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)
|
|
@ -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()
|
|
@ -1,106 +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 uuid
|
||||
|
||||
from oslotest import base as test_base
|
||||
from testtools import matchers
|
||||
import webob
|
||||
import webob.dec
|
||||
|
||||
from oslo_middleware import request_id
|
||||
|
||||
|
||||
class AltHeader(request_id.RequestId):
|
||||
compat_headers = ["x-compute-req-id", "x-silly-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.body.decode('utf-8'), res_req_id)
|
||||
|
||||
def test_compat_headers(self):
|
||||
"""Test that compat headers are set
|
||||
|
||||
Compat headers might exist on a super class to support
|
||||
previous API contracts. This ensures that you can set that to
|
||||
a list of headers and those values are the same as the
|
||||
request_id.
|
||||
|
||||
"""
|
||||
@webob.dec.wsgify
|
||||
def application(req):
|
||||
return req.environ[request_id.ENV_REQUEST_ID]
|
||||
|
||||
app = AltHeader(application)
|
||||
req = webob.Request.blank('/test')
|
||||
res = req.get_response(app)
|
||||
|
||||
res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID)
|
||||
|
||||
self.assertEqual(res.headers.get("x-compute-req-id"), res_req_id)
|
||||
self.assertEqual(res.headers.get("x-silly-id"), res_req_id)
|
||||
|
||||
def test_global_request_id_set(self):
|
||||
"""Test that global request_id is set."""
|
||||
@webob.dec.wsgify
|
||||
def application(req):
|
||||
return req.environ[request_id.GLOBAL_REQ_ID]
|
||||
|
||||
global_req = "req-%s" % uuid.uuid4()
|
||||
app = request_id.RequestId(application)
|
||||
req = webob.Request.blank(
|
||||
'/test',
|
||||
headers={"X-OpenStack-Request-ID": global_req})
|
||||
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')
|
||||
# global-request-id in request environ is returned as response body
|
||||
self.assertEqual(res.body.decode('utf-8'), global_req)
|
||||
self.assertNotEqual(res.body.decode('utf-8'), res_req_id)
|
||||
|
||||
def test_global_request_id_drop(self):
|
||||
"""Test that bad format ids are dropped.
|
||||
|
||||
This ensures that badly formatted ids are dropped entirely.
|
||||
"""
|
||||
@webob.dec.wsgify
|
||||
def application(req):
|
||||
return req.environ.get(request_id.GLOBAL_REQ_ID)
|
||||
|
||||
global_req = "req-%s-bad" % uuid.uuid4()
|
||||
app = request_id.RequestId(application)
|
||||
req = webob.Request.blank(
|
||||
'/test',
|
||||
headers={"X-OpenStack-Request-ID": global_req})
|
||||
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')
|
||||
# global-request-id in request environ is returned as response body
|
||||
self.assertEqual(res.body.decode('utf-8'), '')
|
|
@ -1,102 +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, bytes_read)
|
||||
|
||||
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, bytes_read)
|
||||
|
||||
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(200, response.status_int)
|
||||
|
||||
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(413, response.status_int)
|
|
@ -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')
|
||||
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')
|
|
@ -1,142 +0,0 @@
|
|||
# Copyright (c) 2016 Cisco Systems
|
||||
#
|
||||
# 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
|
||||
import statsd
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from oslo_middleware import stats
|
||||
|
||||
|
||||
class TestStaticMethods(test_base.BaseTestCase):
|
||||
|
||||
def test_removes_uuid(self):
|
||||
# Generate a long-format UUID (standard form).
|
||||
id = str(uuid.uuid4())
|
||||
path = "foo.{uuid}.bar".format(uuid=id)
|
||||
stat = stats.StatsMiddleware.strip_uuid(path)
|
||||
self.assertEqual("foo.bar", stat)
|
||||
|
||||
def test_removes_short_uuid(self):
|
||||
id = uuid.uuid4().hex
|
||||
path = "foo.{uuid}.bar".format(uuid=id)
|
||||
stat = stats.StatsMiddleware.strip_short_uuid(path)
|
||||
self.assertEqual("foo.bar", stat)
|
||||
|
||||
def test_strips_dots_from_version(self):
|
||||
path = "/v1.2/foo.bar/bar.foo"
|
||||
stat = stats.StatsMiddleware.strip_dot_from_version(path)
|
||||
self.assertEqual("/v12/foo.bar/bar.foo", stat)
|
||||
|
||||
|
||||
class TestStatsMiddleware(test_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestStatsMiddleware, self).setUp()
|
||||
self.patch(statsd, 'StatsClient', mock.MagicMock())
|
||||
|
||||
def make_stats_middleware(self, stat_name=None, stats_host=None,
|
||||
remove_uuid=False, remove_short_uuid=False):
|
||||
if stat_name is None:
|
||||
stat_name = uuid.uuid4().hex
|
||||
if stats_host is None:
|
||||
stats_host = uuid.uuid4().hex
|
||||
|
||||
conf = dict(
|
||||
name=stat_name,
|
||||
stats_host=stats_host,
|
||||
remove_uuid=remove_uuid,
|
||||
remove_short_uuid=remove_short_uuid,
|
||||
)
|
||||
|
||||
@webob.dec.wsgify
|
||||
def fake_application(req):
|
||||
return 'Hello, World'
|
||||
|
||||
return stats.StatsMiddleware(fake_application, conf)
|
||||
|
||||
def perform_request(self, app, path, method):
|
||||
req = webob.Request.blank(path, method=method)
|
||||
return req.get_response(app)
|
||||
|
||||
def test_sends_counter_to_statsd(self):
|
||||
app = self.make_stats_middleware()
|
||||
path = '/test/foo/bar'
|
||||
|
||||
self.perform_request(app, path, 'GET')
|
||||
|
||||
expected_stat = "{name}.{method}.{path}".format(
|
||||
name=app.stat_name, method='GET',
|
||||
path=path.lstrip('/').replace('/', '.'))
|
||||
app.statsd.timer.assert_called_once_with(expected_stat)
|
||||
|
||||
def test_strips_uuid_if_configured(self):
|
||||
app = self.make_stats_middleware(remove_uuid=True)
|
||||
random_uuid = str(uuid.uuid4())
|
||||
path = '/foo/{uuid}/bar'.format(uuid=random_uuid)
|
||||
|
||||
self.perform_request(app, path, 'GET')
|
||||
|
||||
expected_stat = "{name}.{method}.foo.bar".format(
|
||||
name=app.stat_name, method='GET')
|
||||
app.statsd.timer.assert_called_once_with(expected_stat)
|
||||
|
||||
def test_strips_short_uuid_if_configured(self):
|
||||
app = self.make_stats_middleware(remove_short_uuid=True)
|
||||
random_uuid = uuid.uuid4().hex
|
||||
path = '/foo/{uuid}/bar'.format(uuid=random_uuid)
|
||||
|
||||
self.perform_request(app, path, 'GET')
|
||||
|
||||
expected_stat = "{name}.{method}.foo.bar".format(
|
||||
name=app.stat_name, method='GET')
|
||||
app.statsd.timer.assert_called_once_with(expected_stat)
|
||||
|
||||
def test_strips_both_uuid_types_if_configured(self):
|
||||
app = self.make_stats_middleware(
|
||||
remove_uuid=True, remove_short_uuid=True)
|
||||
random_short_uuid = uuid.uuid4().hex
|
||||
random_uuid = str(uuid.uuid4())
|
||||
path = '/foo/{uuid}/bar/{short_uuid}'.format(
|
||||
uuid=random_uuid, short_uuid=random_short_uuid)
|
||||
|
||||
self.perform_request(app, path, 'GET')
|
||||
|
||||
expected_stat = "{name}.{method}.foo.bar".format(
|
||||
name=app.stat_name, method='GET')
|
||||
app.statsd.timer.assert_called_once_with(expected_stat)
|
||||
|
||||
def test_always_mutates_version_id(self):
|
||||
app = self.make_stats_middleware()
|
||||
path = '/v2.1/foo/bar'
|
||||
|
||||
self.perform_request(app, path, 'GET')
|
||||
|
||||
expected_stat = "{name}.{method}.v21.foo.bar".format(
|
||||
name=app.stat_name, method='GET')
|
||||
app.statsd.timer.assert_called_once_with(expected_stat)
|
||||
|
||||
def test_empty_path_has_sane_stat_name(self):
|
||||
app = self.make_stats_middleware()
|
||||
path = '/'
|
||||
|
||||
self.perform_request(app, path, 'GET')
|
||||
|
||||
expected_stat = "{name}.{method}".format(
|
||||
name=app.stat_name, method='GET')
|
||||
app.statsd.timer.assert_called_once_with(expected_stat)
|
|
@ -1,18 +0,0 @@
|
|||
# Copyright 2016 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import pbr.version
|
||||
|
||||
version_info = pbr.version.VersionInfo('oslo.middleware')
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
other:
|
||||
- Switch to reno for managing release notes.
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
This adds a new ``compat_headers`` class attribute to the
|
||||
``RequestId`` middleware. That allows projects like Nova that have
|
||||
API contracts on alternative request-id headers to adopt the oslo
|
||||
``RequestId`` middleware but still retain their API contract.
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
This adds support for ``global_request_id`` to the ``RequestId``
|
||||
middleware. An inbound header of ``X-OpenStack-Request-ID`` is
|
||||
accepted as long as it is of the format ``req-$uuid``, and made
|
||||
available to oslo.context. This will allow for cross project
|
||||
request id tracking.
|
|
@ -1,281 +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.
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'reno.sphinxext',
|
||||
'openstackdocstheme'
|
||||
]
|
||||
|
||||
# openstackdocstheme options
|
||||
repository_name = 'openstack/oslo.middleware'
|
||||
bug_project = 'oslo.middleware'
|
||||
bug_tag = ''
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'oslo.middleware Release Notes'
|
||||
copyright = u'2016, oslo.middleware Developers'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
from oslo_middleware.version import version_info as oslo_middleware_version
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = oslo_middleware_version.version_string_with_vcs()
|
||||
# The short X.Y version.
|
||||
version = oslo_middleware_version.canonical_version_string()
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
# keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'openstackdocs'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
# html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'oslo.middlewareReleaseNotesDoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'oslo.middlewareReleaseNotes.tex',
|
||||
u'oslo.middleware Release Notes Documentation',
|
||||
u'oslo.middleware Developers', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'oslo.middlewareReleaseNotes',
|
||||
u'oslo.middleware Release Notes Documentation',
|
||||
[u'oslo.middleware Developers'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'oslo.middlewareReleaseNotes',
|
||||
u'oslo.middleware Release Notes Documentation',
|
||||
u'oslo.middleware Developers', 'oslo.middlewareReleaseNotes',
|
||||
'The library includes components that can be injected into wsgi pipelines'
|
||||
' to intercept request/response flows.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
# -- Options for Internationalization output ------------------------------
|
||||
locale_dirs = ['locale/']
|
|
@ -1,9 +0,0 @@
|
|||
=============================
|
||||
oslo.middleware Release Notes
|
||||
=============================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
unreleased
|
||||
ocata
|
|
@ -1,27 +0,0 @@
|
|||
# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: oslo.middleware Release Notes 3.20.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-11-02 02:56+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-10-22 06:02+0000\n"
|
||||
"Last-Translator: Gérald LONLAS <g.lonlas@gmail.com>\n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
||||
|
||||
msgid "Other Notes"
|
||||
msgstr "Autres notes"
|
||||
|
||||
msgid "Switch to reno for managing release notes."
|
||||
msgstr "Commence à utiliser reno pour la gestion des notes de release"
|
||||
|
||||
msgid "Unreleased Release Notes"
|
||||
msgstr "Note de release pour les changements non déployées"
|
||||
|
||||
msgid "oslo.middleware Release Notes"
|
||||
msgstr "Note de release pour oslo.middleware"
|
|
@ -1,6 +0,0 @@
|
|||
===================================
|
||||
Ocata Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/ocata
|
|
@ -1,5 +0,0 @@
|
|||
==========================
|
||||
Unreleased Release Notes
|
||||
==========================
|
||||
|
||||
.. release-notes::
|
|
@ -1,15 +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!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 # BSD License (3 clause)
|
||||
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
|
||||
oslo.context>=2.14.0 # Apache-2.0
|
||||
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||
oslo.utils>=3.20.0 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
stevedore>=1.20.0 # Apache-2.0
|
||||
WebOb>=1.7.1 # MIT
|
||||
debtcollector>=1.2.0 # Apache-2.0
|
||||
statsd>=3.2.1 # MIT
|
76
setup.cfg
76
setup.cfg
|
@ -1,76 +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 = https://docs.openstack.org/oslo.middleware/latest/
|
||||
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.5
|
||||
|
||||
[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 = oslo_middleware.opts:list_opts_healthcheck
|
||||
|
||||
oslo.middleware.healthcheck =
|
||||
disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck
|
||||
disable_by_files_ports = oslo_middleware.healthcheck.disable_by_file:DisableByFilesPortsHealthcheck
|
||||
|
||||
paste.app_factory =
|
||||
healthcheck = oslo_middleware:Healthcheck.app_factory
|
||||
|
||||
paste.filter_factory =
|
||||
catch_errors = oslo_middleware:CatchErrors.factory
|
||||
correlation_id = oslo_middleware:CorrelationId.factory
|
||||
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
|
||||
warning-is-error = 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
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
29
setup.py
29
setup.py
|
@ -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>=2.0.0'],
|
||||
pbr=True)
|
|
@ -1,13 +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.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||
mock>=2.0 # BSD
|
||||
openstackdocstheme>=1.11.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
sphinx>=1.6.2 # BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
reno!=2.3.1,>=1.8.0 # Apache-2.0
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Client constraint file contains this client version pin that is in conflict
|
||||
# with installing the client from source. We should remove the version pin in
|
||||
# the constraints file before applying it for from-source installation.
|
||||
|
||||
CONSTRAINTS_FILE="$1"
|
||||
shift 1
|
||||
|
||||
set -e
|
||||
|
||||
# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
|
||||
# published to logs.openstack.org for easy debugging.
|
||||
localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
|
||||
|
||||
if [[ "$CONSTRAINTS_FILE" != http* ]]; then
|
||||
CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
|
||||
fi
|
||||
# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
|
||||
curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
|
||||
|
||||
pip install -c"$localfile" openstack-requirements
|
||||
|
||||
# This is the main purpose of the script: Allow local installation of
|
||||
# the current repo. It is listed in constraints file and thus any
|
||||
# install will be constrained and we need to unconstrain it.
|
||||
edit-constraints "$localfile" -- "$CLIENT_NAME"
|
||||
|
||||
pip install -c"$localfile" -U "$@"
|
||||
exit $?
|
45
tox.ini
45
tox.ini
|
@ -1,45 +0,0 @@
|
|||
[tox]
|
||||
minversion = 2.0
|
||||
envlist = py35,py27,pypy,pep8
|
||||
|
||||
[testenv]
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
BRANCH_NAME=master
|
||||
CLIENT_NAME=oslo.middleware
|
||||
install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[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
|
||||
|
||||
[testenv:releasenotes]
|
||||
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
Loading…
Reference in New Issue