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:
Tony Breeds 2017-09-12 16:06:33 -06:00
parent 1cf39ee5c3
commit c979629a70
80 changed files with 14 additions and 6322 deletions

View File

@ -1,8 +0,0 @@
[run]
branch = True
source = oslo_middleware
omit = oslo_middleware/tests/*
[report]
ignore_errors = True
precision = 2

55
.gitignore vendored
View File

@ -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

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/oslo.middleware.git

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
oslo.middleware Style Commandments
==================================
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/

175
LICENSE
View File

@ -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.

14
README Normal file
View File

@ -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.

View File

@ -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

View File

@ -1 +0,0 @@
[python: **.py]

View File

@ -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.

View File

@ -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

View File

@ -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}

View File

@ -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`

View File

@ -1,5 +0,0 @@
==============
Contributing
==============
.. include:: ../../../CONTRIBUTING.rst

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -1,19 +0,0 @@
=====
API
=====
.. automodule:: oslo_middleware
:members:
Configuration Options
=====================
RequestBodySizeLimiter
~~~~~~~~~~~~~~~~~~~~~~
.. show-options:: oslo.middleware.sizelimit
SSLMiddleware
~~~~~~~~~~~~~
.. show-options:: oslo.middleware.ssl

View File

@ -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/

View File

@ -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:

View File

@ -1,8 +0,0 @@
==============================
oslo.middleware Reference
==============================
.. toctree::
:glob:
*

View File

@ -1 +0,0 @@
.. include:: ../../../ChangeLog

View File

@ -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__)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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 &#39;/tmp/dead&#39; 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 &#34;oslo_middleware/healthcheck/__main__.py&#34;, line 94, in &lt;module&gt;
main()
File &#34;oslo_middleware/healthcheck/__main__.py&#34;, 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 &#34;oslo_middleware/healthcheck/__main__.py&#34;, line 94, in &lt;module&gt;
main()
File &#34;oslo_middleware/healthcheck/__main__.py&#34;, 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)

View File

@ -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()

View File

@ -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)

View File

@ -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.'),
]

View File

@ -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)

View File

@ -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"""

View File

@ -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"

View File

@ -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ß."

View File

@ -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"

View File

@ -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."

View File

@ -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"

View File

@ -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."

View File

@ -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))
]

View File

@ -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

View File

@ -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

View File

@ -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'])

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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())

View File

@ -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)

View File

@ -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()

View File

@ -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'), '')

View File

@ -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)

View File

@ -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')

View File

@ -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)

View File

@ -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')

View File

@ -1,3 +0,0 @@
---
other:
- Switch to reno for managing release notes.

View File

@ -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.

View File

@ -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.

View File

@ -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/']

View File

@ -1,9 +0,0 @@
=============================
oslo.middleware Release Notes
=============================
.. toctree::
:maxdepth: 1
unreleased
ocata

View File

@ -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"

View File

@ -1,6 +0,0 @@
===================================
Ocata Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/ocata

View File

@ -1,5 +0,0 @@
==========================
Unreleased Release Notes
==========================
.. release-notes::

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
View File

@ -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