summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--doc/source/cors.rst34
-rw-r--r--oslo_middleware/base.py14
-rw-r--r--oslo_middleware/cors.py20
-rw-r--r--oslo_middleware/healthcheck/__init__.py203
-rw-r--r--oslo_middleware/healthcheck/__main__.py69
-rw-r--r--oslo_middleware/healthcheck/disable_by_file.py4
-rw-r--r--oslo_middleware/http_proxy_to_wsgi.py10
-rw-r--r--oslo_middleware/locale/oslo_middleware-log-error.pot25
-rw-r--r--oslo_middleware/locale/oslo_middleware.pot25
-rw-r--r--oslo_middleware/opts.py29
-rw-r--r--oslo_middleware/ssl.py2
-rw-r--r--oslo_middleware/tests/test_base.py15
-rw-r--r--oslo_middleware/tests/test_correlation_id.py4
-rw-r--r--oslo_middleware/tests/test_cors.py237
-rw-r--r--oslo_middleware/tests/test_healthcheck.py27
-rw-r--r--oslo_middleware/tests/test_http_proxy_to_wsgi.py23
-rw-r--r--oslo_middleware/tests/test_request_id.py2
-rw-r--r--oslo_middleware/tests/test_sizelimit.py10
-rw-r--r--oslo_middleware/version.py18
-rw-r--r--releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml3
-rw-r--r--releasenotes/source/_static/.placeholder0
-rw-r--r--releasenotes/source/_templates/.placeholder0
-rw-r--r--releasenotes/source/conf.py273
-rw-r--r--releasenotes/source/index.rst8
-rw-r--r--releasenotes/source/unreleased.rst5
-rw-r--r--requirements.txt10
-rw-r--r--setup.cfg1
-rw-r--r--test-requirements.txt9
-rw-r--r--tox.ini3
30 files changed, 831 insertions, 255 deletions
diff --git a/.gitignore b/.gitignore
index ed88334..baab79c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,6 @@ ChangeLog
# Editors
*~
.*.swp
+
+# reno build
+releasenotes/build
diff --git a/doc/source/cors.rst b/doc/source/cors.rst
index dbd93a3..ea19d9e 100644
--- a/doc/source/cors.rst
+++ b/doc/source/cors.rst
@@ -45,39 +45,15 @@ domain, using oslo_config::
app = cors.CORS(your_wsgi_application, cfg.CONF)
-In your application's config file, then include a default configuration block
+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=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
-
-This middleware permits you to override the rules for multiple
-`allowed_origin`'s. 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::
-
- [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
+ allow_headers=X-Custom-Header
+ expose_headers=X-Custom-Header
Configuration for pastedeploy
-----------------------------
@@ -90,8 +66,8 @@ will add CORS support.::
allowed_origin=https://website.example.com:443,https://website2.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
+ 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
diff --git a/oslo_middleware/base.py b/oslo_middleware/base.py
index a98a058..27689a1 100644
--- a/oslo_middleware/base.py
+++ b/oslo_middleware/base.py
@@ -17,10 +17,22 @@
from inspect import getargspec
import webob.dec
+import webob.request
+import webob.response
from oslo_config import cfg
+class NoContentTypeResponse(webob.response.Response):
+
+ default_content_type = None # prevents webob assigning content type
+
+
+class NoContentTypeRequest(webob.request.Request):
+
+ ResponseClass = NoContentTypeResponse
+
+
class ConfigurableMiddleware(object):
"""Base WSGI middleware wrapper.
@@ -106,7 +118,7 @@ class ConfigurableMiddleware(object):
"""Do whatever you'd like to the response."""
return response
- @webob.dec.wsgify
+ @webob.dec.wsgify(RequestClass=NoContentTypeRequest)
def __call__(self, req):
response = self.process_request(req)
if response:
diff --git a/oslo_middleware/cors.py b/oslo_middleware/cors.py
index ffd54ed..b53ad45 100644
--- a/oslo_middleware/cors.py
+++ b/oslo_middleware/cors.py
@@ -12,17 +12,16 @@
# implied. See the License for the specific language governing permissions and
# limitations under the License.
-# Default allowed headers
import copy
from debtcollector import moves
import logging
+import debtcollector
from oslo_config import cfg
from oslo_middleware import base
import six
-import webob.dec
import webob.exc
-import webob.response
+
LOG = logging.getLogger(__name__)
@@ -100,11 +99,6 @@ class InvalidOriginError(Exception):
'CORS request from origin \'%s\' not permitted.' % origin)
-class _NoContentTypeResponse(webob.response.Response):
-
- default_content_type = None # prevents webob assigning content type
-
-
class CORS(base.ConfigurableMiddleware):
"""CORS Middleware.
@@ -205,6 +199,11 @@ class CORS(base.ConfigurableMiddleware):
# 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])
@@ -226,6 +225,9 @@ class CORS(base.ConfigurableMiddleware):
# 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:
@@ -330,7 +332,7 @@ class CORS(base.ConfigurableMiddleware):
# 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 = _NoContentTypeResponse(status=webob.exc.HTTPOk.code)
+ 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:
diff --git a/oslo_middleware/healthcheck/__init__.py b/oslo_middleware/healthcheck/__init__.py
index a821233..ce2b03a 100644
--- a/oslo_middleware/healthcheck/__init__.py
+++ b/oslo_middleware/healthcheck/__init__.py
@@ -52,32 +52,187 @@ def _expand_template(contents, params):
class Healthcheck(base.ConfigurableMiddleware):
"""Healthcheck middleware used for monitoring.
- If the path is /healthcheck, it will respond 200 with "OK" as the body.
- Or 503 with the reason as the body if one of the backend report
- an application issue.
+ If the path is ``/healthcheck``, it will respond 200 with "OK" as
+ the body. Or a 503 with the reason as the body if one of the backends
+ reports an application issue.
This is useful for the following reasons:
- 1. Load balancers can 'ping' this url to determine service availability.
- 2. Provides an endpoint that is similar to 'mod_status' in apache which
- can provide details (or no details, depending on if configured) about
- the activity of the server.
-
- Example requests/responses:
-
- $ curl -i -X HEAD "http://0.0.0.0:8775/status"
- HTTP/1.1 204 No Content
- Content-Type: text/plain; charset=UTF-8
- Content-Length: 0
- Date: Fri, 11 Sep 2015 18:55:08 GMT
-
- $ curl -i "http://0.0.0.0:8775/status"
- HTTP/1.1 200 OK
- Content-Type: text/plain; charset=UTF-8
- Content-Length: 2
- Date: Fri, 11 Sep 2015 18:55:43 GMT
-
- OK
+ * 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:
@@ -92,7 +247,6 @@ class Healthcheck(base.ConfigurableMiddleware):
[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:
@@ -118,7 +272,6 @@ class Healthcheck(base.ConfigurableMiddleware):
More details on available backends and their configuration can be found
on this page: :doc:`healthcheck_plugins`.
-
"""
NAMESPACE = "oslo.middleware.healthcheck"
diff --git a/oslo_middleware/healthcheck/__main__.py b/oslo_middleware/healthcheck/__main__.py
new file mode 100644
index 0000000..217fff6
--- /dev/null
+++ b/oslo_middleware/healthcheck/__main__.py
@@ -0,0 +1,69 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import argparse
+
+from six.moves import SimpleHTTPServer # noqa
+from six.moves import socketserver
+import webob
+
+from oslo_middleware import healthcheck
+
+
+class HttpHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ def do_GET(self):
+ @webob.dec.wsgify
+ def dummy_application(req):
+ return 'test'
+ app = healthcheck.Healthcheck(dummy_application, {'detailed': True})
+ req = webob.Request.blank("/healthcheck", accept='text/html',
+ method='GET')
+ res = req.get_response(app)
+ self.send_response(res.status_code)
+ for header_name, header_value in res.headerlist:
+ self.send_header(header_name, header_value)
+ self.end_headers()
+ self.wfile.write(res.body)
+ self.wfile.close()
+
+
+def positive_int(blob):
+ value = int(blob)
+ if value < 0:
+ msg = "%r is not a positive integer" % blob
+ raise argparse.ArgumentTypeError(msg)
+ return value
+
+
+def create_server(port=0):
+ handler = HttpHandler
+ server = socketserver.TCPServer(("", port), handler)
+ return server
+
+
+def main(args=None):
+ """Runs a basic http server to show healthcheck functionality."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-p", "--port",
+ help="Unused port to run the tiny"
+ " http server on (or zero to select a"
+ " random unused port)",
+ type=positive_int, required=True)
+ args = parser.parse_args(args=args)
+ server = create_server(args.port)
+ print("Serving at port: %s" % server.server_address[1])
+ server.serve_forever()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/oslo_middleware/healthcheck/disable_by_file.py b/oslo_middleware/healthcheck/disable_by_file.py
index 8a0403b..7fbb14b 100644
--- a/oslo_middleware/healthcheck/disable_by_file.py
+++ b/oslo_middleware/healthcheck/disable_by_file.py
@@ -52,7 +52,9 @@ class DisableByFilesPortsHealthcheck(pluginbase.HealthcheckBaseExtension):
for port_path in paths.split(","):
port_path = port_path.strip()
if port_path:
- port, path = port_path.split(":")
+ # 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)
diff --git a/oslo_middleware/http_proxy_to_wsgi.py b/oslo_middleware/http_proxy_to_wsgi.py
index ad61401..4d68bcf 100644
--- a/oslo_middleware/http_proxy_to_wsgi.py
+++ b/oslo_middleware/http_proxy_to_wsgi.py
@@ -19,7 +19,7 @@ from oslo_middleware import base
OPTS = [
cfg.BoolOpt('enable_proxy_headers_parsing',
default=False,
- help="Wether the application is behind a proxy or not. "
+ help="Whether the application is behind a proxy or not. "
"This determines if the middleware should parse the "
"headers or not.")
]
@@ -71,6 +71,10 @@ class HTTPProxyToWSGI(base.ConfigurableMiddleware):
if forwarded_host:
req.environ['HTTP_HOST'] = forwarded_host
+ forwarded_for = proxy.get("for")
+ if forwarded_for:
+ req.environ['REMOTE_ADDR'] = forwarded_for
+
else:
# World before RFC7239
forwarded_proto = req.environ.get("HTTP_X_FORWARDED_PROTO")
@@ -81,6 +85,10 @@ class HTTPProxyToWSGI(base.ConfigurableMiddleware):
if forwarded_host:
req.environ['HTTP_HOST'] = forwarded_host
+ forwarded_for = req.environ.get("HTTP_X_FORWARDED_FOR")
+ if forwarded_for:
+ req.environ['REMOTE_ADDR'] = forwarded_for
+
v = req.environ.get("HTTP_X_FORWARDED_PREFIX")
if v:
req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME']
diff --git a/oslo_middleware/locale/oslo_middleware-log-error.pot b/oslo_middleware/locale/oslo_middleware-log-error.pot
deleted file mode 100644
index 6df63c0..0000000
--- a/oslo_middleware/locale/oslo_middleware-log-error.pot
+++ /dev/null
@@ -1,25 +0,0 @@
-# Translations template for oslo.middleware.
-# Copyright (C) 2016 ORGANIZATION
-# This file is distributed under the same license as the oslo.middleware
-# project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: oslo.middleware 3.7.1.dev18\n"
-"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2016-04-21 06:02+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.2.0\n"
-
-#: oslo_middleware/catch_errors.py:40
-#, python-format
-msgid "An error occurred during processing the request: %s"
-msgstr ""
-
diff --git a/oslo_middleware/locale/oslo_middleware.pot b/oslo_middleware/locale/oslo_middleware.pot
deleted file mode 100644
index 009ca69..0000000
--- a/oslo_middleware/locale/oslo_middleware.pot
+++ /dev/null
@@ -1,25 +0,0 @@
-# Translations template for oslo.middleware.
-# Copyright (C) 2016 ORGANIZATION
-# This file is distributed under the same license as the oslo.middleware
-# project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: oslo.middleware 3.7.1.dev18\n"
-"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2016-04-21 06:02+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.2.0\n"
-
-#: oslo_middleware/sizelimit.py:59 oslo_middleware/sizelimit.py:73
-#: oslo_middleware/sizelimit.py:90
-msgid "Request is too large."
-msgstr ""
-
diff --git a/oslo_middleware/opts.py b/oslo_middleware/opts.py
index 1fe8c89..e66e723 100644
--- a/oslo_middleware/opts.py
+++ b/oslo_middleware/opts.py
@@ -17,7 +17,8 @@ __all__ = [
'list_opts',
'list_opts_sizelimit',
'list_opts_ssl',
- 'list_opts_cors'
+ 'list_opts_cors',
+ 'list_opts_http_proxy_to_wsgi',
]
@@ -25,6 +26,7 @@ import copy
import itertools
from oslo_middleware import cors
+from oslo_middleware import http_proxy_to_wsgi
from oslo_middleware import sizelimit
from oslo_middleware import ssl
@@ -53,6 +55,7 @@ def list_opts():
list_opts_sizelimit(),
list_opts_ssl(),
list_opts_cors(),
+ list_opts_http_proxy_to_wsgi(),
)
)
@@ -128,3 +131,27 @@ def list_opts_cors():
('cors', copy.deepcopy(cors.CORS_OPTS)),
('cors.subdomain', copy.deepcopy(cors.CORS_OPTS))
]
+
+
+def list_opts_http_proxy_to_wsgi():
+ """Return a list of oslo.config options for http_proxy_to_wsgi.
+
+ The returned list includes all oslo.config options which may be registered
+ at runtime by the library.
+
+ Each element of the list is a tuple. The first element is the name of the
+ group under which the list of elements in the second element will be
+ registered. A group name of None corresponds to the [DEFAULT] group in
+ config files.
+
+ This function is also discoverable via the 'oslo.middleware' entry point
+ under the 'oslo.config.opts' namespace.
+
+ The purpose of this is to allow tools like the Oslo sample config file
+ generator to discover the options exposed to users by this library.
+
+ :returns: a list of (group_name, opts) tuples
+ """
+ return [
+ ('oslo_middleware', copy.deepcopy(http_proxy_to_wsgi.OPTS)),
+ ]
diff --git a/oslo_middleware/ssl.py b/oslo_middleware/ssl.py
index 7853dc1..f90e954 100644
--- a/oslo_middleware/ssl.py
+++ b/oslo_middleware/ssl.py
@@ -20,7 +20,7 @@ OPTS = [
deprecated_for_removal=True,
help="The HTTP Header that will be used to determine what "
"the original request protocol scheme was, even if it was "
- "hidden by an SSL termination proxy.")
+ "hidden by a SSL termination proxy.")
]
diff --git a/oslo_middleware/tests/test_base.py b/oslo_middleware/tests/test_base.py
index ad48da5..57eee94 100644
--- a/oslo_middleware/tests/test_base.py
+++ b/oslo_middleware/tests/test_base.py
@@ -58,15 +58,26 @@ class TestBase(BaseTestCase):
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, {})
+ self.assertEqual({}, app.conf)
def test_paste_deploy_configurable(self):
app = ConfigurableMiddlewareTest.factory(
{'global': True}, local=True)(application)
- self.assertEqual(app.conf, {'global': True, 'local': True})
+ self.assertEqual({'global': True, 'local': True}, app.conf)
class NoRequestBase(Middleware):
diff --git a/oslo_middleware/tests/test_correlation_id.py b/oslo_middleware/tests/test_correlation_id.py
index 6dde5d8..63177fa 100644
--- a/oslo_middleware/tests/test_correlation_id.py
+++ b/oslo_middleware/tests/test_correlation_id.py
@@ -40,7 +40,7 @@ class CorrelationIdTest(test_base.BaseTestCase):
middleware = correlation_id.CorrelationId(app)
middleware(req)
- self.assertEqual(req.headers.get("X_CORRELATION_ID"), "fake_uuid")
+ self.assertEqual("fake_uuid", req.headers.get("X_CORRELATION_ID"))
def test_process_request_should_not_regenerate_correlation_id(self):
app = mock.Mock()
@@ -50,4 +50,4 @@ class CorrelationIdTest(test_base.BaseTestCase):
middleware = correlation_id.CorrelationId(app)
middleware(req)
- self.assertEqual(req.headers.get("X_CORRELATION_ID"), "correlation_id")
+ self.assertEqual("correlation_id", req.headers.get("X_CORRELATION_ID"))
diff --git a/oslo_middleware/tests/test_cors.py b/oslo_middleware/tests/test_cors.py
index 5e0822f..e8276f3 100644
--- a/oslo_middleware/tests/test_cors.py
+++ b/oslo_middleware/tests/test_cors.py
@@ -79,7 +79,7 @@ class CORSTestBase(test_base.BaseTestCase):
"""
# Assert response status.
- self.assertEqual(response.status, status)
+ self.assertEqual(status, response.status)
# Assert the Access-Control-Allow-Origin header.
self.assertHeader(response,
@@ -163,7 +163,7 @@ class CORSTestDefaultOverrides(CORSTestBase):
for opt in cors.CORS_OPTS:
if opt.dest in self.override_opts:
- self.assertEqual(opt.default, self.override_opts[opt.dest])
+ self.assertEqual(self.override_opts[opt.dest], opt.default)
def test_invalid_default_option(self):
"""Assert that using set_defaults only permits valid options."""
@@ -183,39 +183,39 @@ class CORSTestDefaultOverrides(CORSTestBase):
# Check the global configuration for expected values:
gc = self.config.cors
- self.assertEqual(gc.allowed_origin, ['http://valid.example.com'])
- self.assertEqual(gc.allow_credentials,
- self.override_opts['allow_credentials'])
- self.assertEqual(gc.expose_headers,
- self.override_opts['expose_headers'])
- self.assertEqual(gc.max_age, 10)
- self.assertEqual(gc.allow_methods,
- self.override_opts['allow_methods'])
- self.assertEqual(gc.allow_headers,
- self.override_opts['allow_headers'])
+ self.assertEqual(['http://valid.example.com'], gc.allowed_origin)
+ self.assertEqual(self.override_opts['allow_credentials'],
+ gc.allow_credentials)
+ self.assertEqual(self.override_opts['expose_headers'],
+ gc.expose_headers)
+ self.assertEqual(10, gc.max_age)
+ self.assertEqual(self.override_opts['allow_methods'],
+ gc.allow_methods)
+ self.assertEqual(self.override_opts['allow_headers'],
+ gc.allow_headers)
# Check the child configuration for expected values:
cc = self.config['cors.override_creds']
- self.assertEqual(cc.allowed_origin, ['http://creds.example.com'])
+ self.assertEqual(['http://creds.example.com'], cc.allowed_origin)
self.assertTrue(cc.allow_credentials)
- self.assertEqual(cc.expose_headers,
- self.override_opts['expose_headers'])
- self.assertEqual(cc.max_age, 10)
- self.assertEqual(cc.allow_methods,
- self.override_opts['allow_methods'])
- self.assertEqual(cc.allow_headers,
- self.override_opts['allow_headers'])
+ self.assertEqual(self.override_opts['expose_headers'],
+ cc.expose_headers)
+ self.assertEqual(10, cc.max_age)
+ self.assertEqual(self.override_opts['allow_methods'],
+ cc.allow_methods)
+ self.assertEqual(self.override_opts['allow_headers'],
+ cc.allow_headers)
# Check the other child configuration for expected values:
ec = self.config['cors.override_headers']
- self.assertEqual(ec.allowed_origin, ['http://headers.example.com'])
- self.assertEqual(ec.allow_credentials,
- self.override_opts['allow_credentials'])
- self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2'])
- self.assertEqual(ec.max_age, 10)
- self.assertEqual(ec.allow_methods,
- self.override_opts['allow_methods'])
- self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2'])
+ self.assertEqual(['http://headers.example.com'], ec.allowed_origin)
+ self.assertEqual(self.override_opts['allow_credentials'],
+ ec.allow_credentials)
+ self.assertEqual(['X-Header-1', 'X-Header-2'], ec.expose_headers)
+ self.assertEqual(10, ec.max_age)
+ self.assertEqual(self.override_opts['allow_methods'],
+ ec.allow_methods)
+ self.assertEqual(['X-Header-1', 'X-Header-2'], ec.allow_headers)
class CORSTestFilterFactory(CORSTestBase):
@@ -358,68 +358,69 @@ class CORSRegularRequestTest(CORSTestBase):
# Confirm global configuration
gc = self.config.cors
- self.assertEqual(gc.allowed_origin, ['http://valid.example.com'])
- self.assertEqual(gc.allow_credentials, False)
- self.assertEqual(gc.expose_headers, [])
- self.assertEqual(gc.max_age, None)
- self.assertEqual(gc.allow_methods, ['GET'])
- self.assertEqual(gc.allow_headers, [])
+ self.assertEqual(['http://valid.example.com'], gc.allowed_origin)
+ self.assertEqual(False, gc.allow_credentials)
+ self.assertEqual([], gc.expose_headers)
+ self.assertEqual(None, gc.max_age)
+ self.assertEqual(['GET'], gc.allow_methods)
+ self.assertEqual([], gc.allow_headers)
# Confirm credentials overrides.
cc = self.config['cors.credentials']
- self.assertEqual(cc.allowed_origin, ['http://creds.example.com'])
- self.assertEqual(cc.allow_credentials, True)
- self.assertEqual(cc.expose_headers, gc.expose_headers)
- self.assertEqual(cc.max_age, gc.max_age)
- self.assertEqual(cc.allow_methods, gc.allow_methods)
- self.assertEqual(cc.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://creds.example.com'], cc.allowed_origin)
+ self.assertEqual(True, cc.allow_credentials)
+ self.assertEqual(gc.expose_headers, cc.expose_headers)
+ self.assertEqual(gc.max_age, cc.max_age)
+ self.assertEqual(gc.allow_methods, cc.allow_methods)
+ self.assertEqual(gc.allow_headers, cc.allow_headers)
# Confirm exposed-headers overrides.
ec = self.config['cors.exposed-headers']
- self.assertEqual(ec.allowed_origin, ['http://headers.example.com'])
- self.assertEqual(ec.allow_credentials, gc.allow_credentials)
- self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2'])
- self.assertEqual(ec.max_age, gc.max_age)
- self.assertEqual(ec.allow_methods, gc.allow_methods)
- self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2'])
+ self.assertEqual(['http://headers.example.com'], ec.allowed_origin)
+ self.assertEqual(gc.allow_credentials, ec.allow_credentials)
+ self.assertEqual(['X-Header-1', 'X-Header-2'], ec.expose_headers)
+ self.assertEqual(gc.max_age, ec.max_age)
+ self.assertEqual(gc.allow_methods, ec.allow_methods)
+ self.assertEqual(['X-Header-1', 'X-Header-2'], ec.allow_headers)
# Confirm cached overrides.
chc = self.config['cors.cached']
- self.assertEqual(chc.allowed_origin, ['http://cached.example.com'])
- self.assertEqual(chc.allow_credentials, gc.allow_credentials)
- self.assertEqual(chc.expose_headers, gc.expose_headers)
- self.assertEqual(chc.max_age, 3600)
- self.assertEqual(chc.allow_methods, gc.allow_methods)
- self.assertEqual(chc.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://cached.example.com'], chc.allowed_origin)
+ self.assertEqual(gc.allow_credentials, chc.allow_credentials)
+ self.assertEqual(gc.expose_headers, chc.expose_headers)
+ self.assertEqual(3600, chc.max_age)
+ self.assertEqual(gc.allow_methods, chc.allow_methods)
+ self.assertEqual(gc.allow_headers, chc.allow_headers)
# Confirm get-only overrides.
goc = self.config['cors.get-only']
- self.assertEqual(goc.allowed_origin, ['http://get.example.com'])
- self.assertEqual(goc.allow_credentials, gc.allow_credentials)
- self.assertEqual(goc.expose_headers, gc.expose_headers)
- self.assertEqual(goc.max_age, gc.max_age)
- self.assertEqual(goc.allow_methods, ['GET'])
- self.assertEqual(goc.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://get.example.com'], goc.allowed_origin)
+ self.assertEqual(gc.allow_credentials, goc.allow_credentials)
+ self.assertEqual(gc.expose_headers, goc.expose_headers)
+ self.assertEqual(gc.max_age, goc.max_age)
+ self.assertEqual(['GET'], goc.allow_methods)
+ self.assertEqual(gc.allow_headers, goc.allow_headers)
# Confirm all-methods overrides.
ac = self.config['cors.all-methods']
- self.assertEqual(ac.allowed_origin, ['http://all.example.com'])
- self.assertEqual(ac.allow_credentials, gc.allow_credentials)
- self.assertEqual(ac.expose_headers, gc.expose_headers)
- self.assertEqual(ac.max_age, gc.max_age)
- self.assertEqual(ac.allow_methods,
- ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'])
- self.assertEqual(ac.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://all.example.com'], ac.allowed_origin)
+ self.assertEqual(gc.allow_credentials, ac.allow_credentials)
+ self.assertEqual(gc.expose_headers, ac.expose_headers)
+ self.assertEqual(gc.max_age, ac.max_age)
+ self.assertEqual(['GET', 'PUT', 'POST', 'DELETE', 'HEAD'],
+ ac.allow_methods)
+ self.assertEqual(gc.allow_headers, ac.allow_headers)
# Confirm duplicate domains.
ac = self.config['cors.duplicate']
- self.assertEqual(ac.allowed_origin, ['http://domain1.example.com',
- 'http://domain2.example.com'])
- self.assertEqual(ac.allow_credentials, gc.allow_credentials)
- self.assertEqual(ac.expose_headers, gc.expose_headers)
- self.assertEqual(ac.max_age, gc.max_age)
- self.assertEqual(ac.allow_methods, gc.allow_methods)
- self.assertEqual(ac.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://domain1.example.com',
+ 'http://domain2.example.com'],
+ ac.allowed_origin)
+ self.assertEqual(gc.allow_credentials, ac.allow_credentials)
+ self.assertEqual(gc.expose_headers, ac.expose_headers)
+ self.assertEqual(gc.max_age, ac.max_age)
+ self.assertEqual(gc.allow_methods, ac.allow_methods)
+ self.assertEqual(gc.allow_headers, ac.allow_headers)
def test_no_origin_header(self):
"""CORS Specification Section 6.1.1
@@ -683,49 +684,49 @@ class CORSPreflightRequestTest(CORSTestBase):
# Confirm credentials overrides.
cc = self.config['cors.credentials']
- self.assertEqual(cc.allowed_origin, ['http://creds.example.com'])
- self.assertEqual(cc.allow_credentials, True)
- self.assertEqual(cc.expose_headers, gc.expose_headers)
- self.assertEqual(cc.max_age, gc.max_age)
- self.assertEqual(cc.allow_methods, gc.allow_methods)
- self.assertEqual(cc.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://creds.example.com'], cc.allowed_origin)
+ self.assertEqual(True, cc.allow_credentials)
+ self.assertEqual(gc.expose_headers, cc.expose_headers)
+ self.assertEqual(gc.max_age, cc.max_age)
+ self.assertEqual(gc.allow_methods, cc.allow_methods)
+ self.assertEqual(gc.allow_headers, cc.allow_headers)
# Confirm exposed-headers overrides.
ec = self.config['cors.exposed-headers']
- self.assertEqual(ec.allowed_origin, ['http://headers.example.com'])
- self.assertEqual(ec.allow_credentials, gc.allow_credentials)
- self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2'])
- self.assertEqual(ec.max_age, gc.max_age)
- self.assertEqual(ec.allow_methods, gc.allow_methods)
- self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2'])
+ self.assertEqual(['http://headers.example.com'], ec.allowed_origin)
+ self.assertEqual(gc.allow_credentials, ec.allow_credentials)
+ self.assertEqual(['X-Header-1', 'X-Header-2'], ec.expose_headers)
+ self.assertEqual(gc.max_age, ec.max_age)
+ self.assertEqual(gc.allow_methods, ec.allow_methods)
+ self.assertEqual(['X-Header-1', 'X-Header-2'], ec.allow_headers)
# Confirm cached overrides.
chc = self.config['cors.cached']
- self.assertEqual(chc.allowed_origin, ['http://cached.example.com'])
- self.assertEqual(chc.allow_credentials, gc.allow_credentials)
- self.assertEqual(chc.expose_headers, gc.expose_headers)
- self.assertEqual(chc.max_age, 3600)
- self.assertEqual(chc.allow_methods, gc.allow_methods)
- self.assertEqual(chc.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://cached.example.com'], chc.allowed_origin)
+ self.assertEqual(gc.allow_credentials, chc.allow_credentials)
+ self.assertEqual(gc.expose_headers, chc.expose_headers)
+ self.assertEqual(3600, chc.max_age)
+ self.assertEqual(gc.allow_methods, chc.allow_methods)
+ self.assertEqual(gc.allow_headers, chc.allow_headers)
# Confirm get-only overrides.
goc = self.config['cors.get-only']
- self.assertEqual(goc.allowed_origin, ['http://get.example.com'])
- self.assertEqual(goc.allow_credentials, gc.allow_credentials)
- self.assertEqual(goc.expose_headers, gc.expose_headers)
- self.assertEqual(goc.max_age, gc.max_age)
- self.assertEqual(goc.allow_methods, ['GET'])
- self.assertEqual(goc.allow_headers, gc.allow_headers)
+ self.assertEqual(['http://get.example.com'], goc.allowed_origin)
+ self.assertEqual(gc.allow_credentials, goc.allow_credentials)
+ self.assertEqual(gc.expose_headers, goc.expose_headers)
+ self.assertEqual(gc.max_age, goc.max_age)
+ self.assertEqual(['GET'], goc.allow_methods)
+ self.assertEqual(gc.allow_headers, goc.allow_headers)
# Confirm all-methods overrides.
ac = self.config['cors.all-methods']
- self.assertEqual(ac.allowed_origin, ['http://all.example.com'])
- self.assertEqual(ac.allow_credentials, gc.allow_credentials)
- self.assertEqual(ac.expose_headers, gc.expose_headers)
- self.assertEqual(ac.max_age, gc.max_age)
+ self.assertEqual(['http://all.example.com'], ac.allowed_origin)
+ self.assertEqual(gc.allow_credentials, ac.allow_credentials)
+ self.assertEqual(gc.expose_headers, ac.expose_headers)
+ self.assertEqual(gc.max_age, ac.max_age)
self.assertEqual(ac.allow_methods,
['GET', 'PUT', 'POST', 'DELETE', 'HEAD'])
- self.assertEqual(ac.allow_headers, gc.allow_headers)
+ self.assertEqual(gc.allow_headers, ac.allow_headers)
def test_no_origin_header(self):
"""CORS Specification Section 6.2.1
@@ -1141,10 +1142,10 @@ class CORSPreflightRequestTest(CORSTestBase):
# If the regular CORS handling catches this request, it should set
# the allow credentials header. This makes sure that it doesn't.
self.assertNotIn('Access-Control-Allow-Credentials', response.headers)
- self.assertEqual(response.headers['Access-Control-Allow-Origin'],
- test_origin)
- self.assertEqual(response.headers['X-Server-Generated-Response'],
- '1')
+ self.assertEqual(test_origin,
+ response.headers['Access-Control-Allow-Origin'])
+ self.assertEqual('1',
+ response.headers['X-Server-Generated-Response'])
# If the application returns an OPTIONS response without CORS
# headers, assert that we apply headers.
@@ -1191,22 +1192,22 @@ class CORSTestWildcard(CORSTestBase):
# Confirm global configuration
gc = self.config.cors
- self.assertEqual(gc.allowed_origin, ['http://default.example.com'])
- self.assertEqual(gc.allow_credentials, True)
- self.assertEqual(gc.expose_headers, [])
- self.assertEqual(gc.max_age, None)
- self.assertEqual(gc.allow_methods, ['GET', 'PUT', 'POST', 'DELETE',
- 'HEAD'])
- self.assertEqual(gc.allow_headers, [])
+ self.assertEqual(['http://default.example.com'], gc.allowed_origin)
+ self.assertEqual(True, gc.allow_credentials)
+ self.assertEqual([], gc.expose_headers)
+ self.assertEqual(None, gc.max_age)
+ self.assertEqual(['GET', 'PUT', 'POST', 'DELETE', 'HEAD'],
+ gc.allow_methods)
+ self.assertEqual([], gc.allow_headers)
# Confirm all-methods overrides.
ac = self.config['cors.wildcard']
- self.assertEqual(ac.allowed_origin, ['*'])
- self.assertEqual(gc.allow_credentials, True)
- self.assertEqual(ac.expose_headers, gc.expose_headers)
- self.assertEqual(ac.max_age, gc.max_age)
- self.assertEqual(ac.allow_methods, ['GET'])
- self.assertEqual(ac.allow_headers, gc.allow_headers)
+ self.assertEqual(['*'], ac.allowed_origin)
+ self.assertEqual(True, gc.allow_credentials)
+ self.assertEqual(gc.expose_headers, ac.expose_headers)
+ self.assertEqual(gc.max_age, ac.max_age)
+ self.assertEqual(['GET'], ac.allow_methods)
+ self.assertEqual(gc.allow_headers, ac.allow_headers)
def test_wildcard_domain(self):
"""CORS Specification, Wildcards
diff --git a/oslo_middleware/tests/test_healthcheck.py b/oslo_middleware/tests/test_healthcheck.py
index 3ffb03f..df73969 100644
--- a/oslo_middleware/tests/test_healthcheck.py
+++ b/oslo_middleware/tests/test_healthcheck.py
@@ -13,12 +13,39 @@
# License for the specific language governing permissions and limitations
# under the License.
+import threading
+import time
+
import mock
from oslotest import base as test_base
+import requests
import webob.dec
import webob.exc
from oslo_middleware import healthcheck
+from oslo_middleware.healthcheck import __main__
+
+
+class HealthcheckMainTests(test_base.BaseTestCase):
+
+ def test_startup_response(self):
+ server = __main__.create_server(0)
+ th = threading.Thread(target=server.serve_forever)
+ th.start()
+ self.addCleanup(server.shutdown)
+ while True:
+ try:
+ # Connecting on 0.0.0.0 is not allowed on windows
+ # The operating system will return WSAEADDRNOTAVAIL which
+ # in turn will throw a requests.ConnectionError
+ r = requests.get("http://127.0.0.1:%s" % (
+ server.server_address[1]))
+ except requests.ConnectionError:
+ # Server hasn't started up yet, try again in a few.
+ time.sleep(1)
+ else:
+ self.assertEqual(200, r.status_code)
+ break
class HealthcheckTests(test_base.BaseTestCase):
diff --git a/oslo_middleware/tests/test_http_proxy_to_wsgi.py b/oslo_middleware/tests/test_http_proxy_to_wsgi.py
index 26baa77..1554ece 100644
--- a/oslo_middleware/tests/test_http_proxy_to_wsgi.py
+++ b/oslo_middleware/tests/test_http_proxy_to_wsgi.py
@@ -103,6 +103,29 @@ class TestHTTPProxyToWSGI(test_base.BaseTestCase):
response = self.request.get_response(self.middleware)
self.assertEqual(b"https://example.com:8043/bla", response.body)
+ def test_forwarded_for_headers(self):
+ @webob.dec.wsgify()
+ def fake_app(req):
+ return req.environ['REMOTE_ADDR']
+
+ self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGIMiddleware(
+ fake_app)
+ forwarded_for_addr = '1.2.3.4'
+ forwarded_addr = '8.8.8.8'
+
+ # If both X-Forwarded-For and Fowarded headers are present, it should
+ # use the Forwarded header and ignore the X-Forwarded-For header.
+ self.request.headers['Forwarded'] = (
+ "for=%s;proto=https;host=example.com:8043" % (forwarded_addr))
+ self.request.headers['X-Forwarded-For'] = forwarded_for_addr
+ response = self.request.get_response(self.middleware)
+ self.assertEqual(forwarded_addr.encode(), response.body)
+
+ # Now if only X-Forwarded-For header is present, it should be used.
+ del self.request.headers['Forwarded']
+ response = self.request.get_response(self.middleware)
+ self.assertEqual(forwarded_for_addr.encode(), response.body)
+
class TestHTTPProxyToWSGIDisabled(test_base.BaseTestCase):
diff --git a/oslo_middleware/tests/test_request_id.py b/oslo_middleware/tests/test_request_id.py
index 76f3696..039b7af 100644
--- a/oslo_middleware/tests/test_request_id.py
+++ b/oslo_middleware/tests/test_request_id.py
@@ -36,4 +36,4 @@ class RequestIdTest(test_base.BaseTestCase):
res_req_id = res_req_id.decode('utf-8')
self.assertThat(res_req_id, matchers.StartsWith('req-'))
# request-id in request environ is returned as response body
- self.assertEqual(res_req_id, res.body.decode('utf-8'))
+ self.assertEqual(res.body.decode('utf-8'), res_req_id)
diff --git a/oslo_middleware/tests/test_sizelimit.py b/oslo_middleware/tests/test_sizelimit.py
index dc29cd0..42622ae 100644
--- a/oslo_middleware/tests/test_sizelimit.py
+++ b/oslo_middleware/tests/test_sizelimit.py
@@ -29,7 +29,7 @@ class TestLimitingReader(test_base.BaseTestCase):
for chunk in sizelimit.LimitingReader(data, BYTES):
bytes_read += len(chunk)
- self.assertEqual(bytes_read, BYTES)
+ self.assertEqual(BYTES, bytes_read)
bytes_read = 0
data = six.StringIO("*" * BYTES)
@@ -39,7 +39,7 @@ class TestLimitingReader(test_base.BaseTestCase):
bytes_read += 1
byte = reader.read(1)
- self.assertEqual(bytes_read, BYTES)
+ self.assertEqual(BYTES, bytes_read)
def test_read_default_value(self):
BYTES = 1024
@@ -93,16 +93,16 @@ class TestRequestBodySizeLimiter(test_base.BaseTestCase):
self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE
self.request.body = b"0" * self.MAX_REQUEST_BODY_SIZE
response = self.request.get_response(self.middleware)
- self.assertEqual(response.status_int, 200)
+ 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(response.status_int, 413)
+ self.assertEqual(413, response.status_int)
def test_request_too_large_no_content_length(self):
self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1)
self.request.headers['Content-Length'] = None
response = self.request.get_response(self.middleware)
- self.assertEqual(response.status_int, 413)
+ self.assertEqual(413, response.status_int)
diff --git a/oslo_middleware/version.py b/oslo_middleware/version.py
new file mode 100644
index 0000000..f73224f
--- /dev/null
+++ b/oslo_middleware/version.py
@@ -0,0 +1,18 @@
+# Copyright 2016 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import pbr.version
+
+version_info = pbr.version.VersionInfo('oslo_middleware')
diff --git a/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml b/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml
new file mode 100644
index 0000000..46a2da6
--- /dev/null
+++ b/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml
@@ -0,0 +1,3 @@
+---
+other:
+ - Switch to reno for managing release notes. \ No newline at end of file
diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/source/_static/.placeholder
diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/source/_templates/.placeholder
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
new file mode 100644
index 0000000..4bbee08
--- /dev/null
+++ b/releasenotes/source/conf.py
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'oslosphinx',
+ 'reno.sphinxext',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'oslo.middleware Release Notes'
+copyright = u'2016, oslo.middleware Developers'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+from oslo_middleware.version import version_info as oslo_middleware_version
+
+# The full version, including alpha/beta/rc tags.
+release = oslo_middleware_version.version_string_with_vcs()
+# The short X.Y version.
+version = oslo_middleware_version.canonical_version_string()
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'oslo.middlewareReleaseNotesDoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ ('index', 'oslo.middlewareReleaseNotes.tex',
+ u'oslo.middleware Release Notes Documentation',
+ u'oslo.middleware Developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'oslo.middlewareReleaseNotes',
+ u'oslo.middleware Release Notes Documentation',
+ [u'oslo.middleware Developers'], 1)
+]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'oslo.middlewareReleaseNotes',
+ u'oslo.middleware Release Notes Documentation',
+ u'oslo.middleware Developers', 'oslo.middlewareReleaseNotes',
+ 'The library includes components that can be injected into wsgi pipelines'
+ ' to intercept request/response flows.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+# texinfo_no_detailmenu = False
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
new file mode 100644
index 0000000..d19561d
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,8 @@
+=============================
+oslo.middleware Release Notes
+=============================
+
+ .. toctree::
+ :maxdepth: 1
+
+ unreleased
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
new file mode 100644
index 0000000..5860a46
--- /dev/null
+++ b/releasenotes/source/unreleased.rst
@@ -0,0 +1,5 @@
+==========================
+ Unreleased Release Notes
+==========================
+
+.. release-notes::
diff --git a/requirements.txt b/requirements.txt
index f03122f..db1bfbd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,11 +4,11 @@
pbr>=1.6 # Apache-2.0
Jinja2>=2.8 # BSD License (3 clause)
-oslo.config>=3.9.0 # Apache-2.0
-oslo.context>=2.2.0 # Apache-2.0
+oslo.config>=3.14.0 # Apache-2.0
+oslo.context>=2.9.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
-oslo.utils>=3.5.0 # Apache-2.0
+oslo.utils>=3.16.0 # Apache-2.0
six>=1.9.0 # MIT
-stevedore>=1.9.0 # Apache-2.0
-WebOb>=1.2.3 # MIT
+stevedore>=1.17.1 # Apache-2.0
+WebOb>=1.6.0 # MIT
debtcollector>=1.2.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 87be3b2..d2a7686 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,6 +28,7 @@ oslo.config.opts =
oslo.middleware.cors = oslo_middleware.opts:list_opts_cors
oslo.middleware.sizelimit = oslo_middleware.opts:list_opts_sizelimit
oslo.middleware.ssl = oslo_middleware.opts:list_opts_ssl
+ oslo.middleware.http_proxy_to_wsgi = oslo_middleware.opts:list_opts_http_proxy_to_wsgi
oslo.middleware.healthcheck =
disable_by_file = oslo_middleware.healthcheck.disable_by_file:DisableByFileHealthcheck
diff --git a/test-requirements.txt b/test-requirements.txt
index fc60e42..b241c09 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,11 +2,12 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-fixtures<2.0,>=1.3.1 # Apache-2.0/BSD
+fixtures>=3.0.0 # Apache-2.0/BSD
hacking<0.11,>=0.10.0
-mock>=1.2 # BSD
-oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
+mock>=2.0 # BSD
+oslosphinx>=4.7.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
-sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
+sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
testtools>=1.4.0 # MIT
coverage>=3.6 # Apache-2.0
+reno>=1.8.0 # Apache2
diff --git a/tox.ini b/tox.ini
index 9ff7d03..41c43e8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -35,3 +35,6 @@ import_exceptions = oslo_middleware._i18n
# of the requirements.txt files
deps = pip_missing_reqs
commands = pip-missing-reqs -d --ignore-module=oslo_middleware* --ignore-module=pkg_resources --ignore-file=oslo_middleware/tests/* oslo_middleware
+
+[testenv:releasenotes]
+commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html