Cleanup keystone.server.flask.application
Remove a chunk of the compat code for legacy dispatching. This moves the logging about the request to it's own before_request function. Change-Id: I0b1a4ca9a95489e410f055ff47f3399feba3a8f1 Partial-Bug: #1776504
This commit is contained in:
parent
ee9b035cf1
commit
e666839bc1
|
@ -12,58 +12,21 @@
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import collections
|
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from oslo_middleware import healthcheck
|
from oslo_middleware import healthcheck
|
||||||
import routes
|
|
||||||
import werkzeug.wsgi
|
import werkzeug.wsgi
|
||||||
|
|
||||||
import keystone.api
|
import keystone.api
|
||||||
from keystone.common import wsgi as keystone_wsgi
|
|
||||||
from keystone.server.flask.request_processing import json_body
|
from keystone.server.flask.request_processing import json_body
|
||||||
|
from keystone.server.flask.request_processing import req_logging
|
||||||
# TODO(morgan): _MOVED_API_PREFIXES to be removed when the legacy dispatch
|
|
||||||
# support is removed.
|
|
||||||
_MOVED_API_PREFIXES = frozenset(
|
|
||||||
['auth',
|
|
||||||
'credentials',
|
|
||||||
'domains',
|
|
||||||
'ec2tokens',
|
|
||||||
'endpoints',
|
|
||||||
'groups',
|
|
||||||
'OS-EP-FILTER',
|
|
||||||
'OS-FEDERATION',
|
|
||||||
'OS-INHERIT',
|
|
||||||
'OS-OAUTH1',
|
|
||||||
'OS-REVOKE',
|
|
||||||
'OS-SIMPLE-CERT',
|
|
||||||
'OS-TRUST',
|
|
||||||
'limits',
|
|
||||||
'policy',
|
|
||||||
'projects',
|
|
||||||
'regions',
|
|
||||||
'registered_limits',
|
|
||||||
'role_assignments',
|
|
||||||
'role_inferences',
|
|
||||||
'roles',
|
|
||||||
's3tokens',
|
|
||||||
'services',
|
|
||||||
'system',
|
|
||||||
'users',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
ALL_API_ROUTERS = []
|
|
||||||
|
|
||||||
|
|
||||||
def fail_gracefully(f):
|
def fail_gracefully(f):
|
||||||
"""Log exceptions and aborts."""
|
"""Log exceptions and aborts."""
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
|
@ -80,90 +43,6 @@ def fail_gracefully(f):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class KeystoneDispatcherMiddleware(werkzeug.wsgi.DispatcherMiddleware):
|
|
||||||
"""Allows one to mount middlewares or applications in a WSGI application.
|
|
||||||
|
|
||||||
This is useful if you want to combine multiple WSGI applications::
|
|
||||||
|
|
||||||
app = DispatcherMiddleware(app, {
|
|
||||||
'/app2': app2,
|
|
||||||
'/app3': app3
|
|
||||||
})
|
|
||||||
|
|
||||||
This is a modified version of the werkzeurg.wsgi.DispatchMiddleware to
|
|
||||||
handle the "SCRIPT_NAME" and "PATH_INFO" mangling in a way that is
|
|
||||||
compatible with the way paste.deploy and routes.Mapper works. For
|
|
||||||
Migration from legacy routes.Mapper to native flask blueprints, we are
|
|
||||||
treating each subsystem as their own "app".
|
|
||||||
|
|
||||||
This Dispatcher also logs (debug) if we are dispatching a request to
|
|
||||||
a non-native flask Mapper.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def config(self):
|
|
||||||
return self.app.config
|
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
script = environ.get('PATH_INFO', '')
|
|
||||||
original_script_name = environ.get('SCRIPT_NAME', '')
|
|
||||||
last_element = ''
|
|
||||||
path_info = ''
|
|
||||||
while '/' in script:
|
|
||||||
if script in self.mounts:
|
|
||||||
LOG.debug('Dispatching request to legacy mapper: %s',
|
|
||||||
script)
|
|
||||||
app = self.mounts[script]
|
|
||||||
# NOTE(morgan): Simply because we're doing something "odd"
|
|
||||||
# here and internally routing magically to another "wsgi"
|
|
||||||
# router even though we're already deep in the stack we
|
|
||||||
# need to re-add the last element pulled off. This is 100%
|
|
||||||
# legacy and only applies to the "apps" that make up each
|
|
||||||
# keystone subsystem.
|
|
||||||
#
|
|
||||||
# This middleware is only used in support of the transition
|
|
||||||
# process from webob and home-rolled WSGI framework to
|
|
||||||
# Flask
|
|
||||||
if script.rindex('/') > 0:
|
|
||||||
script, last_element = script.rsplit('/', 1)
|
|
||||||
last_element = '/%s' % last_element
|
|
||||||
environ['SCRIPT_NAME'] = original_script_name + script
|
|
||||||
# Ensure there is only 1 slash between these items, the
|
|
||||||
# mapper gets horribly confused if we have // in there,
|
|
||||||
# which occasionally. As this is temporary to dispatch
|
|
||||||
# to the Legacy mapper, fix the string until we no longer
|
|
||||||
# need this logic.
|
|
||||||
environ['PATH_INFO'] = '%s/%s' % (last_element.rstrip('/'),
|
|
||||||
path_info.strip('/'))
|
|
||||||
break
|
|
||||||
script, last_item = script.rsplit('/', 1)
|
|
||||||
path_info = '/%s%s' % (last_item, path_info)
|
|
||||||
else:
|
|
||||||
app = self.mounts.get(script, self.app)
|
|
||||||
if app != self.app:
|
|
||||||
LOG.debug('Dispatching (fallthrough) request to legacy '
|
|
||||||
'mapper: %s', script)
|
|
||||||
else:
|
|
||||||
LOG.debug('Dispatching back to Flask native app.')
|
|
||||||
environ['SCRIPT_NAME'] = original_script_name + script
|
|
||||||
environ['PATH_INFO'] = path_info
|
|
||||||
|
|
||||||
# NOTE(morgan): remove extra trailing slashes so the mapper can do the
|
|
||||||
# right thing and get the requests mapped to the right place. For
|
|
||||||
# example, "/v3/projects/" is not the same as "/v3/projects". We do not
|
|
||||||
# want to blindly rstrip for the case of '/'.
|
|
||||||
if environ['PATH_INFO'][-1] == '/' and len(environ['PATH_INFO']) > 1:
|
|
||||||
environ['PATH_INFO'] = environ['PATH_INFO'][0:-1]
|
|
||||||
LOG.debug('SCRIPT_NAME: `%s`, PATH_INFO: `%s`',
|
|
||||||
environ['SCRIPT_NAME'], environ['PATH_INFO'])
|
|
||||||
return app(environ, start_response)
|
|
||||||
|
|
||||||
|
|
||||||
class _ComposibleRouterStub(keystone_wsgi.ComposableRouter):
|
|
||||||
def __init__(self, routers):
|
|
||||||
self._routers = routers
|
|
||||||
|
|
||||||
|
|
||||||
def _add_vary_x_auth_token_header(response):
|
def _add_vary_x_auth_token_header(response):
|
||||||
# Add the expected Vary Header, this is run after every request in the
|
# Add the expected Vary Header, this is run after every request in the
|
||||||
# response-phase
|
# response-phase
|
||||||
|
@ -177,11 +56,10 @@ def application_factory(name='public'):
|
||||||
raise RuntimeError('Application name (for base_url lookup) must be '
|
raise RuntimeError('Application name (for base_url lookup) must be '
|
||||||
'either `admin` or `public`.')
|
'either `admin` or `public`.')
|
||||||
|
|
||||||
# NOTE(morgan): The Flask App actually dispatches nothing until we migrate
|
|
||||||
# some routers to Flask-Blueprints, it is simply a placeholder.
|
|
||||||
app = flask.Flask(name)
|
app = flask.Flask(name)
|
||||||
|
|
||||||
# Add core before request functions
|
# Add core before request functions
|
||||||
|
app.before_request(req_logging.log_request_info)
|
||||||
app.before_request(json_body.json_body_before_request)
|
app.before_request(json_body.json_body_before_request)
|
||||||
|
|
||||||
# Add core after request functions
|
# Add core after request functions
|
||||||
|
@ -192,57 +70,22 @@ def application_factory(name='public'):
|
||||||
# We want to bubble up Flask Exceptions (for now)
|
# We want to bubble up Flask Exceptions (for now)
|
||||||
PROPAGATE_EXCEPTIONS=True)
|
PROPAGATE_EXCEPTIONS=True)
|
||||||
|
|
||||||
# TODO(morgan): Convert Subsystems over to Flask-Native, for now, we simply
|
|
||||||
# dispatch to another "application" [e.g "keystone"]
|
|
||||||
# NOTE(morgan): as each router is converted to flask-native blueprint,
|
|
||||||
# remove from this list. WARNING ORDER MATTERS; ordered dict used to
|
|
||||||
# ensure sane ordering of the routers in the legacy-dispatch model.
|
|
||||||
dispatch_map = collections.OrderedDict()
|
|
||||||
|
|
||||||
# Load in Healthcheck and map it to /healthcheck
|
|
||||||
hc_app = healthcheck.Healthcheck.app_factory(
|
|
||||||
{}, oslo_config_project='keystone')
|
|
||||||
dispatch_map['/healthcheck'] = hc_app
|
|
||||||
|
|
||||||
# More legacy code to instantiate all the magic for the dispatchers.
|
|
||||||
# The move to blueprints (FLASK) will allow this to be eliminated.
|
|
||||||
_routers = []
|
|
||||||
sub_routers = []
|
|
||||||
mapper = routes.Mapper()
|
|
||||||
for api_routers in ALL_API_ROUTERS:
|
|
||||||
moved_found = [pfx for
|
|
||||||
pfx in getattr(api_routers, '_path_prefixes', [])
|
|
||||||
if pfx in _MOVED_API_PREFIXES]
|
|
||||||
if moved_found:
|
|
||||||
raise RuntimeError('An API Router is trying to register path '
|
|
||||||
'prefix(s) `%(pfx)s` that is handled by the '
|
|
||||||
'native Flask app. Keystone cannot '
|
|
||||||
'start.' %
|
|
||||||
{'pfx': ', '.join([p for p in moved_found])})
|
|
||||||
|
|
||||||
routers_instance = api_routers.Routers()
|
|
||||||
_routers.append(routers_instance)
|
|
||||||
routers_instance.append_v3_routers(mapper, sub_routers)
|
|
||||||
|
|
||||||
# TODO(morgan): Remove "API version registration". For now this is kept
|
# TODO(morgan): Remove "API version registration". For now this is kept
|
||||||
# for ease of conversion (minimal changes)
|
# for ease of conversion (minimal changes)
|
||||||
keystone.api.discovery.register_version('v3')
|
keystone.api.discovery.register_version('v3')
|
||||||
|
|
||||||
# NOTE(morgan): We add in all the keystone.api blueprints here, this
|
|
||||||
# replaces (as they are implemented) the legacy dispatcher work.
|
|
||||||
for api in keystone.api.__apis__:
|
for api in keystone.api.__apis__:
|
||||||
for api_bp in api.APIs:
|
for api_bp in api.APIs:
|
||||||
api_bp.instantiate_and_register_to_app(app)
|
api_bp.instantiate_and_register_to_app(app)
|
||||||
|
|
||||||
# Build and construct the dispatching for the Legacy dispatching model
|
# Load in Healthcheck and map it to /healthcheck
|
||||||
sub_routers.append(_ComposibleRouterStub(_routers))
|
hc_app = healthcheck.Healthcheck.app_factory(
|
||||||
legacy_dispatcher = keystone_wsgi.ComposingRouter(mapper, sub_routers)
|
{}, oslo_config_project='keystone')
|
||||||
|
|
||||||
for pfx in itertools.chain(*[rtr.Routers._path_prefixes for
|
# Use the simple form of the dispatch middleware, no extra logic needed
|
||||||
rtr in ALL_API_ROUTERS]):
|
# for legacy dispatching. This is to mount /healthcheck at a consistent
|
||||||
dispatch_map['/v3/%s' % pfx] = legacy_dispatcher
|
# place
|
||||||
|
app.wsgi_app = werkzeug.wsgi.DispatcherMiddleware(
|
||||||
app.wsgi_app = KeystoneDispatcherMiddleware(
|
|
||||||
app.wsgi_app,
|
app.wsgi_app,
|
||||||
dispatch_map)
|
{'/healthcheck': hc_app})
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# LOG some debug output about the request. This was originally in the
|
||||||
|
# dispatch middleware
|
||||||
|
|
||||||
|
import flask
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
_ENVIRON_KEYS = ('SCRIPT_NAME', 'PATH_INFO')
|
||||||
|
|
||||||
|
|
||||||
|
def log_request_info():
|
||||||
|
# Add in any extra debug logging about the request that is desired
|
||||||
|
# note that this is executed prior to routing the request to a resource
|
||||||
|
# so the data is somewhat raw.
|
||||||
|
for element in _ENVIRON_KEYS:
|
||||||
|
LOG.debug("environ['%(key)s']: %(value)s",
|
||||||
|
{'key': element,
|
||||||
|
'value': flask.request.environ.get(element, '<<NOT SET>>')})
|
Loading…
Reference in New Issue