Convert Keystone to use Flask
Basic conversion of Keystone's core application to flask framework. This doesn't add much in the way of flask-specific-isms but should get keystone running directly under flask. This implementation does not use paste-deploy. Change-Id: Ib4c1ed3f645dd55fbfb76395263ecdaf605caae7
This commit is contained in:
parent
104717d458
commit
4ec6bc5a44
@ -28,6 +28,7 @@ APP_CRED_RESOURCE_PATH = (
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
_path_prefixes = (APP_CRED_COLLECTION_PATH, 'users',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
app_cred_controller = controllers.ApplicationCredentialV3()
|
||||
|
@ -39,6 +39,9 @@ class Public(wsgi.ComposableRouter):
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('users', 'roles', 'role_inferences', 'projects',
|
||||
'domains', 'system', 'role_assignments', 'OS-INHERIT')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
|
||||
project_controller = controllers.ProjectAssignmentV3()
|
||||
|
@ -19,6 +19,8 @@ from keystone.common import wsgi
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('auth',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
auth_controller = controllers.Auth()
|
||||
|
||||
|
@ -72,6 +72,8 @@ class Routers(wsgi.RoutersBase):
|
||||
PATH_ENDPOINT_GROUP_PROJECTS = PATH_ENDPOINT_GROUPS + (
|
||||
'/projects/{project_id}')
|
||||
|
||||
_path_prefixes = (PATH_PREFIX, 'regions', 'endpoints', 'services')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
regions_controller = controllers.RegionV3()
|
||||
endpoint_filter_controller = controllers.EndpointFilterV3Controller()
|
||||
|
@ -321,6 +321,12 @@ class Application(BaseApplication):
|
||||
|
||||
@classmethod
|
||||
def base_url(cls, context, endpoint_type):
|
||||
# NOTE(morgan): endpoint type must be "admin" or "public" to lookup
|
||||
# the relevant value from the config options. Make the error clearer
|
||||
# if we somehow got here with a different endpoint type.
|
||||
if endpoint_type not in ('admin', 'public'):
|
||||
raise ValueError('PROGRAMMING ERROR: Endpoint type must be '
|
||||
'"admin" or "public".')
|
||||
url = CONF['%s_endpoint' % endpoint_type]
|
||||
|
||||
if url:
|
||||
@ -386,7 +392,7 @@ class Middleware(Application):
|
||||
return cls(app)
|
||||
return _factory
|
||||
|
||||
def __init__(self, application):
|
||||
def __init__(self, application, conf=None):
|
||||
super(Middleware, self).__init__()
|
||||
self.application = application
|
||||
|
||||
|
@ -14,5 +14,6 @@
|
||||
|
||||
from keystone.contrib.ec2 import controllers # noqa
|
||||
from keystone.contrib.ec2.core import * # noqa
|
||||
from keystone.contrib.ec2 import routers # noqa
|
||||
from keystone.contrib.ec2.routers import Ec2Extension # noqa
|
||||
from keystone.contrib.ec2.routers import Ec2ExtensionV3 # noqa
|
||||
from keystone.contrib.ec2.routers import Routers as Ec2ExtensionV3 # noqa
|
||||
|
@ -57,9 +57,11 @@ class Ec2Extension(wsgi.ExtensionRouter):
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
|
||||
class Ec2ExtensionV3(wsgi.V3ExtensionRouter):
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
def add_routes(self, mapper):
|
||||
_path_prefixes = ('ec2tokens', 'users')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
ec2_controller = controllers.Ec2ControllerV3()
|
||||
# validation
|
||||
self._add_resource(
|
||||
|
@ -36,8 +36,11 @@ from keystone import exception
|
||||
from keystone.i18n import _
|
||||
|
||||
|
||||
class S3Extension(wsgi.V3ExtensionRouter):
|
||||
def add_routes(self, mapper):
|
||||
class S3Extension(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('s3tokens',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
controller = S3Controller()
|
||||
# validation
|
||||
self._add_resource(
|
||||
|
16
keystone/contrib/s3/routers.py
Normal file
16
keystone/contrib/s3/routers.py
Normal file
@ -0,0 +1,16 @@
|
||||
# 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 keystone.contrib import s3
|
||||
|
||||
|
||||
Routers = s3.S3Extension
|
@ -21,6 +21,8 @@ from keystone.credential import controllers
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('credentials',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
routers.append(
|
||||
router.Router(controllers.CredentialV3(),
|
||||
|
@ -28,6 +28,8 @@ class Routers(wsgi.RoutersBase):
|
||||
|
||||
PATH_PREFIX = '/OS-ENDPOINT-POLICY'
|
||||
|
||||
_path_prefixes = ('OS-ENDPOINT-POLICY',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
endpoint_policy_controller = controllers.EndpointPolicyV3Controller()
|
||||
|
||||
|
@ -105,6 +105,8 @@ class Routers(wsgi.RoutersBase):
|
||||
|
||||
"""
|
||||
|
||||
_path_prefixes = ('auth', 'OS-FEDERATION')
|
||||
|
||||
def _construct_url(self, suffix):
|
||||
return "/OS-FEDERATION/%s" % suffix
|
||||
|
||||
|
@ -21,6 +21,8 @@ from keystone.identity import controllers
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('users', 'groups')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
user_controller = controllers.UserV3()
|
||||
routers.append(
|
||||
|
@ -18,6 +18,7 @@ from keystone.limit import controllers
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
_path_prefixes = ('registered_limits', 'limits')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
|
||||
|
@ -63,6 +63,8 @@ class Routers(wsgi.RoutersBase):
|
||||
|
||||
"""
|
||||
|
||||
_path_prefixes = ('users', 'OS-OAUTH1')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
consumer_controller = controllers.ConsumerCrudV3()
|
||||
access_token_controller = controllers.AccessTokenCrudV3()
|
||||
|
@ -18,6 +18,8 @@ from keystone.policy import controllers
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('policies',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
policy_controller = controllers.PolicyV3()
|
||||
routers.append(router.Router(policy_controller, 'policies', 'policy',
|
||||
|
@ -23,6 +23,8 @@ from keystone.resource import controllers
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('domains', 'projects')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
routers.append(
|
||||
router.Router(controllers.DomainV3(),
|
||||
|
@ -19,6 +19,8 @@ class Routers(wsgi.RoutersBase):
|
||||
|
||||
PATH_PREFIX = '/OS-REVOKE'
|
||||
|
||||
_path_prefixes = ('OS-REVOKE',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
revoke_controller = controllers.RevokeController()
|
||||
self._add_resource(
|
||||
|
13
keystone/server/flask/__init__.py
Normal file
13
keystone/server/flask/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
# 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 keystone.server.flask.core import * # noqa
|
245
keystone/server/flask/application.py
Normal file
245
keystone/server/flask/application.py
Normal file
@ -0,0 +1,245 @@
|
||||
# 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 __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
import functools
|
||||
import itertools
|
||||
import sys
|
||||
|
||||
import flask
|
||||
from oslo_log import log
|
||||
from oslo_middleware import healthcheck
|
||||
import routes
|
||||
import werkzeug.wsgi
|
||||
|
||||
|
||||
from keystone.application_credential import routers as app_cred_routers
|
||||
from keystone.assignment import routers as assignment_routers
|
||||
from keystone.auth import routers as auth_routers
|
||||
from keystone.catalog import routers as catalog_routers
|
||||
from keystone.common import wsgi as keystone_wsgi
|
||||
from keystone.contrib.ec2 import routers as ec2_routers
|
||||
from keystone.contrib.s3 import routers as s3_routers
|
||||
from keystone.credential import routers as credential_routers
|
||||
from keystone.endpoint_policy import routers as endpoint_policy_routers
|
||||
from keystone.federation import routers as federation_routers
|
||||
from keystone.identity import routers as identity_routers
|
||||
from keystone.limit import routers as limit_routers
|
||||
from keystone.oauth1 import routers as oauth1_routers
|
||||
from keystone.policy import routers as policy_routers
|
||||
from keystone.resource import routers as resource_routers
|
||||
from keystone.revoke import routers as revoke_routers
|
||||
from keystone.token import _simple_cert as simple_cert_ext
|
||||
from keystone.trust import routers as trust_routers
|
||||
from keystone.version import controllers as version_controllers
|
||||
from keystone.version import routers as version_routers
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
ALL_API_ROUTERS = [auth_routers,
|
||||
assignment_routers,
|
||||
catalog_routers,
|
||||
credential_routers,
|
||||
identity_routers,
|
||||
app_cred_routers,
|
||||
limit_routers,
|
||||
policy_routers,
|
||||
resource_routers,
|
||||
trust_routers,
|
||||
revoke_routers,
|
||||
federation_routers,
|
||||
oauth1_routers,
|
||||
endpoint_policy_routers,
|
||||
ec2_routers,
|
||||
s3_routers,
|
||||
# TODO(morganfainberg): Remove the simple_cert router
|
||||
simple_cert_ext]
|
||||
|
||||
|
||||
def fail_gracefully(f):
|
||||
"""Log exceptions and aborts."""
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kw):
|
||||
try:
|
||||
return f(*args, **kw)
|
||||
except Exception as e:
|
||||
LOG.debug(e, exc_info=True)
|
||||
|
||||
# exception message is printed to all logs
|
||||
LOG.critical(e)
|
||||
sys.exit(1)
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
script = environ.get('PATH_INFO', '')
|
||||
original_script_name = environ.get('SCRIPT_NAME', '')
|
||||
last_element = ''
|
||||
path_info = ''
|
||||
# NOTE(morgan): Special Case root documents per version, these *are*
|
||||
# special and should never fall through to the legacy dispatcher, they
|
||||
# must be handled by the version dispatchers.
|
||||
if script not in ('/v3', '/', '/v2.0'):
|
||||
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.
|
||||
#
|
||||
# [OPINION] All subsystems should have been built
|
||||
# as "separate" wsgi apps and dispatched this way. It would
|
||||
# have eliminated a ton of "cross-talk" that has bitten us
|
||||
# in the past.
|
||||
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
|
||||
else:
|
||||
# Special casing for version discovery docs.
|
||||
# REMOVE THIS SPECIAL CASE WHEN VERSION DISCOVERY GOES FLASK NATIVE
|
||||
app = self.mounts.get(script, self.app)
|
||||
if script == '/':
|
||||
# ROOT Version Discovery Doc
|
||||
LOG.debug('Dispatching to legacy root mapper for root version '
|
||||
'discovery document: `%s`', script)
|
||||
environ['SCRIPT_NAME'] = '/'
|
||||
environ['PATH_INFO'] = '/'
|
||||
elif script == '/v3':
|
||||
LOG.debug('Dispatching to legacy mapper for v3 version '
|
||||
'discovery document: `%s`', script)
|
||||
# V3 Version Discovery Doc
|
||||
environ['SCRIPT_NAME'] = '/v3'
|
||||
environ['PATH_INFO'] = '/'
|
||||
else:
|
||||
LOG.debug('Dispatching to flask native app for version '
|
||||
'discovery document: `%s`', script)
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
@fail_gracefully
|
||||
def application_factory(name='public'):
|
||||
if name not in ('admin', 'public'):
|
||||
raise RuntimeError('Application name (for base_url lookup) must be '
|
||||
'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)
|
||||
|
||||
# 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:
|
||||
routers_instance = api_routers.Routers()
|
||||
_routers.append(routers_instance)
|
||||
routers_instance.append_v3_routers(mapper, sub_routers)
|
||||
|
||||
# Add in the v3 version api
|
||||
sub_routers.append(version_routers.VersionV3('public', _routers))
|
||||
version_controllers.register_version('v3')
|
||||
legacy_dispatcher = keystone_wsgi.ComposingRouter(mapper, sub_routers)
|
||||
|
||||
for pfx in itertools.chain(*[rtr.Routers._path_prefixes for
|
||||
rtr in ALL_API_ROUTERS]):
|
||||
dispatch_map['/v3/%s' % pfx] = legacy_dispatcher
|
||||
|
||||
# NOTE(morgan) Move the version routers to Flask Native First! It will
|
||||
# not work well due to how the dispatcher works unless this is first,
|
||||
# otherwise nothing falls through to the native flask app.
|
||||
dispatch_map['/v3'] = legacy_dispatcher
|
||||
|
||||
# NOTE(morgan): The Root Version Discovery Document is special and needs
|
||||
# it's own mapper/router since the v3 one assumes it owns the root due
|
||||
# to legacy paste-isms where /v3 would be routed to APP=/v3, PATH=/
|
||||
root_version_disc_mapper = routes.Mapper()
|
||||
root_version_disc_router = version_routers.Versions(name)
|
||||
root_dispatcher = keystone_wsgi.ComposingRouter(
|
||||
root_version_disc_mapper, [root_version_disc_router])
|
||||
dispatch_map['/'] = root_dispatcher
|
||||
|
||||
application = KeystoneDispatcherMiddleware(
|
||||
app,
|
||||
dispatch_map)
|
||||
return application
|
153
keystone/server/flask/core.py
Normal file
153
keystone/server/flask/core.py
Normal file
@ -0,0 +1,153 @@
|
||||
# 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 os
|
||||
|
||||
import oslo_i18n
|
||||
from oslo_log import log
|
||||
import stevedore
|
||||
|
||||
|
||||
# NOTE(dstanek): i18n.enable_lazy() must be called before
|
||||
# keystone.i18n._() is called to ensure it has the desired lazy lookup
|
||||
# behavior. This includes cases, like keystone.exceptions, where
|
||||
# keystone.i18n._() is called at import time.
|
||||
oslo_i18n.enable_lazy()
|
||||
|
||||
from keystone.common import profiler
|
||||
import keystone.conf
|
||||
from keystone.server import common
|
||||
from keystone.server.flask import application
|
||||
|
||||
# NOTE(morgan): Middleware Named Tuple with the following values:
|
||||
# * "namespace": namespace for the entry_point
|
||||
# * "ep": the entry-point name
|
||||
# * "conf": extra config data for the entry_point (None or Dict)
|
||||
_Middleware = collections.namedtuple('LoadableMiddleware',
|
||||
'namespace, ep, conf')
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
# NOTE(morgan): ORDER HERE IS IMPORTANT! The middleware will process the
|
||||
# request in this list's order.
|
||||
_APP_MIDDLEWARE = (
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='cors',
|
||||
conf={'oslo_config_project': 'keystone'}),
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='sizelimit',
|
||||
conf={}),
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='http_proxy_to_wsgi',
|
||||
conf={}),
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='url_normalize',
|
||||
conf={}),
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='json_body',
|
||||
conf={}),
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='osprofiler',
|
||||
conf={}),
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='request_id',
|
||||
conf={}),
|
||||
_Middleware(namespace='keystone.server_middleware',
|
||||
ep='build_auth_context',
|
||||
conf={}),
|
||||
)
|
||||
|
||||
|
||||
def _get_config_files(env=None):
|
||||
if env is None:
|
||||
env = os.environ
|
||||
|
||||
dirname = env.get('OS_KEYSTONE_CONFIG_DIR', '').strip()
|
||||
|
||||
files = [s.strip() for s in
|
||||
env.get('OS_KEYSTONE_CONFIG_FILES', '').split(';') if s.strip()]
|
||||
|
||||
if dirname:
|
||||
if not files:
|
||||
files = ['keystone.conf']
|
||||
files = [os.path.join(dirname, fname) for fname in files]
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def setup_app_middleware(application):
|
||||
# NOTE(morgan): Load the middleware, in reverse order, we wrap the app
|
||||
# explicitly; reverse order to ensure the first element in _APP_MIDDLEWARE
|
||||
# processes the request first.
|
||||
|
||||
for mw in reversed(_APP_MIDDLEWARE):
|
||||
# TODO(morgan): Explore moving this to ExtensionManager, but we
|
||||
# want to be super careful about what middleware we load and in
|
||||
# what order. DriverManager gives us that capability and only loads
|
||||
# the entry points we care about rather than all of them.
|
||||
|
||||
# Load via Stevedore, initialize the class via the factory so we can
|
||||
# initialize the "loaded" entrypoint with the currently bound
|
||||
# object pointed at "application". We may need to eventually move away
|
||||
# from the "factory" mechanism.
|
||||
loaded = stevedore.DriverManager(
|
||||
mw.namespace, mw.ep, invoke_on_load=False)
|
||||
# NOTE(morgan): global_conf (args[0]) to the factory is always empty
|
||||
# and local_conf (args[1]) will be the mw.conf dict. This allows for
|
||||
# configuration to be passed for middleware such as oslo CORS which
|
||||
# expects oslo_config_project or "allowed_origin" to be in the
|
||||
# local_conf, this is all a hold-over from paste-ini and pending
|
||||
# reworking/removal(s)
|
||||
factory_func = loaded.driver.factory({}, **mw.conf)
|
||||
application = factory_func(application)
|
||||
return application
|
||||
|
||||
|
||||
def initialize_application(name, post_log_configured_function=lambda: None,
|
||||
config_files=None):
|
||||
possible_topdir = os.path.normpath(os.path.join(
|
||||
os.path.abspath(__file__),
|
||||
os.pardir,
|
||||
os.pardir,
|
||||
os.pardir))
|
||||
|
||||
dev_conf = os.path.join(possible_topdir,
|
||||
'etc',
|
||||
'keystone.conf')
|
||||
if not config_files:
|
||||
config_files = None
|
||||
if os.path.exists(dev_conf):
|
||||
config_files = [dev_conf]
|
||||
|
||||
common.configure(config_files=config_files)
|
||||
|
||||
# Log the options used when starting if we're in debug mode...
|
||||
if CONF.debug:
|
||||
CONF.log_opt_values(log.getLogger(CONF.prog), log.DEBUG)
|
||||
|
||||
post_log_configured_function()
|
||||
|
||||
# TODO(morgan): Provide a better mechanism than "loadapp", this was for
|
||||
# paste-deploy specific mechanisms.
|
||||
def loadapp():
|
||||
app = application.application_factory(name)
|
||||
return app
|
||||
|
||||
_unused, app = common.setup_backends(
|
||||
startup_application_fn=loadapp)
|
||||
|
||||
# setup OSprofiler notifier and enable the profiling if that is configured
|
||||
# in Keystone configuration file.
|
||||
profiler.setup(name)
|
||||
|
||||
return setup_app_middleware(app)
|
@ -27,6 +27,7 @@ from keystone.common import profiler
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.server import common
|
||||
from keystone.server import flask as keystone_flask
|
||||
from keystone.version import service as keystone_service
|
||||
|
||||
|
||||
@ -125,10 +126,10 @@ def _get_config_files(env=None):
|
||||
|
||||
|
||||
def initialize_admin_application():
|
||||
return initialize_application(name='admin',
|
||||
config_files=_get_config_files())
|
||||
return keystone_flask.initialize_application(
|
||||
name='admin', config_files=_get_config_files())
|
||||
|
||||
|
||||
def initialize_public_application():
|
||||
return initialize_application(name='main',
|
||||
config_files=_get_config_files())
|
||||
return keystone_flask.initialize_application(
|
||||
name='public', config_files=_get_config_files())
|
||||
|
@ -807,8 +807,8 @@ class TestCase(BaseTestCase):
|
||||
return 'config:%s-paste.ini' % path
|
||||
return config
|
||||
|
||||
def loadapp(self, config, name='main'):
|
||||
return service.loadapp(self._paste_config(config), name=name)
|
||||
def loadapp(self, name='public'):
|
||||
return service.loadapp(name=name)
|
||||
|
||||
def assertCloseEnoughForGovernmentWork(self, a, b, delta=3):
|
||||
"""Assert that two datetimes are nearly equal within a small delta.
|
||||
|
@ -64,10 +64,10 @@ class RestfulTestCase(unit.TestCase):
|
||||
enable_sqlite_foreign_key=enable_sqlite_foreign_key)
|
||||
|
||||
self.public_app = webtest.TestApp(
|
||||
self.loadapp(app_conf, name='main'))
|
||||
self.loadapp(name='public'))
|
||||
self.addCleanup(delattr, self, 'public_app')
|
||||
self.admin_app = webtest.TestApp(
|
||||
self.loadapp(app_conf, name='admin'))
|
||||
self.loadapp(name='admin'))
|
||||
self.addCleanup(delattr, self, 'admin_app')
|
||||
|
||||
def auth_plugin_config_override(self, methods=None, **method_classes):
|
||||
|
@ -25,6 +25,11 @@ PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class EC2ContribCoreV2(rest.RestfulTestCase):
|
||||
def setUp(self):
|
||||
super(EC2ContribCoreV2, self).setUp()
|
||||
# TODO(morgan): remove test class, v2.0 has been deleted
|
||||
self.skipTest('V2.0 has been deleted, test is nolonger valid')
|
||||
|
||||
def config_overrides(self):
|
||||
super(EC2ContribCoreV2, self).config_overrides()
|
||||
|
||||
|
@ -48,6 +48,8 @@ class V2CredentialEc2TestCase(rest.RestfulTestCase):
|
||||
return '/v2.0/users/%s/credentials/OS-EC2' % self.user_id
|
||||
|
||||
def test_ec2_cannot_get_non_ec2_credential(self):
|
||||
# TODO(morgan): remove this test class, V2 has been removed.
|
||||
self.skipTest('V2.0 has been removed, remove the whole test class.')
|
||||
access_key = uuid.uuid4().hex
|
||||
cred_id = utils.hash_access_key(access_key)
|
||||
non_ec2_cred = unit.new_credential_ref(
|
||||
@ -77,6 +79,8 @@ class V2CredentialEc2TestCase(rest.RestfulTestCase):
|
||||
self.assertEqual(int(resp['error']['code']), r.status_code)
|
||||
|
||||
def test_ec2_list_credentials(self):
|
||||
# TODO(morgan): remove this test class, V2 has been removed.
|
||||
self.skipTest('V2.0 has been removed, remove the whole test class.')
|
||||
self._get_ec2_cred()
|
||||
uri = self._get_ec2_cred_uri()
|
||||
r = self.public_request(method='GET', token=self.get_scoped_token(),
|
||||
@ -103,6 +107,8 @@ class V2CredentialEc2TestCase(rest.RestfulTestCase):
|
||||
class V2CredentialEc2Controller(unit.TestCase):
|
||||
def setUp(self):
|
||||
super(V2CredentialEc2Controller, self).setUp()
|
||||
# TODO(morgan): remove the whole test case/class, v2.0 is dead.
|
||||
self.skipTest('V2.0 has been removed, test case is not valid.')
|
||||
self.useFixture(database.Database())
|
||||
self.useFixture(
|
||||
ksfixtures.KeyRepository(
|
||||
|
@ -16,24 +16,21 @@ from testtools import matchers
|
||||
from keystone.tests.unit import core as test
|
||||
|
||||
|
||||
class TestPasteDeploymentEntryPoints(test.TestCase):
|
||||
class TestEntryPoints(test.TestCase):
|
||||
def test_entry_point_middleware(self):
|
||||
"""Assert that our list of expected middleware is present."""
|
||||
expected_names = [
|
||||
'build_auth_context',
|
||||
'cors',
|
||||
'debug',
|
||||
'ec2_extension',
|
||||
'ec2_extension_v3',
|
||||
'json_body',
|
||||
'request_id',
|
||||
's3_extension',
|
||||
'sizelimit',
|
||||
'token_auth',
|
||||
'url_normalize',
|
||||
]
|
||||
|
||||
em = stevedore.ExtensionManager('paste.filter_factory')
|
||||
em = stevedore.ExtensionManager('keystone.server_middleware')
|
||||
|
||||
actual_names = [extension.name for extension in em]
|
||||
|
||||
|
@ -3740,6 +3740,11 @@ class TestAuthJSONExternal(test_v3.RestfulTestCase):
|
||||
|
||||
|
||||
class TestTrustOptional(test_v3.RestfulTestCase):
|
||||
def setUp(self):
|
||||
super(TestTrustOptional, self).setUp()
|
||||
# TODO(morgan): remove this test case, trusts are not optional.
|
||||
self.skipTest('Trusts are no longer optional.')
|
||||
|
||||
def config_overrides(self):
|
||||
super(TestTrustOptional, self).config_overrides()
|
||||
self.config_fixture.config(group='trust', enabled=False)
|
||||
|
@ -1595,7 +1595,7 @@ class FederatedIdentityProviderTests(test_v3.RestfulTestCase):
|
||||
deleted.
|
||||
|
||||
"""
|
||||
url = self.base_url(suffix='/%(idp_id)s/'
|
||||
url = self.base_url(suffix='%(idp_id)s/'
|
||||
'protocols/%(protocol_id)s')
|
||||
resp, idp_id, proto = self._assign_protocol_to_idp(
|
||||
expected_status=http_client.CREATED)
|
||||
|
@ -91,7 +91,6 @@ VERSIONS_RESPONSE = {
|
||||
"versions": {
|
||||
"values": [
|
||||
v3_EXPECTED_RESPONSE,
|
||||
v2_EXPECTED_RESPONSE
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -718,8 +717,8 @@ class VersionTestCase(unit.TestCase):
|
||||
def setUp(self):
|
||||
super(VersionTestCase, self).setUp()
|
||||
self.load_backends()
|
||||
self.public_app = self.loadapp('keystone', 'main')
|
||||
self.admin_app = self.loadapp('keystone', 'admin')
|
||||
self.public_app = self.loadapp('public')
|
||||
self.admin_app = self.loadapp('admin')
|
||||
|
||||
self.admin_port = random.randint(10000, 30000)
|
||||
self.public_port = random.randint(40000, 60000)
|
||||
@ -747,6 +746,8 @@ class VersionTestCase(unit.TestCase):
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost:%s/v3/' % self.public_port)
|
||||
elif version['id'] == 'v2.0':
|
||||
# TODO(morgan): remove this if block in future patch,
|
||||
# v2.0 has been removed.
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost:%s/v2.0/' % self.public_port)
|
||||
self.assertThat(data, _VersionsEqual(expected))
|
||||
@ -762,6 +763,8 @@ class VersionTestCase(unit.TestCase):
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost:%s/v3/' % self.admin_port)
|
||||
elif version['id'] == 'v2.0':
|
||||
# TODO(morgan): remove this if block in future patch,
|
||||
# v2.0 has been removed.
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost:%s/v2.0/' % self.admin_port)
|
||||
self.assertThat(data, _VersionsEqual(expected))
|
||||
@ -781,11 +784,15 @@ class VersionTestCase(unit.TestCase):
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost/v3/')
|
||||
elif version['id'] == 'v2.0':
|
||||
# TODO(morgan): remove this if block in future patch,
|
||||
# v2.0 has been removed.
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost/v2.0/')
|
||||
self.assertThat(data, _VersionsEqual(expected))
|
||||
|
||||
def test_public_version_v2(self):
|
||||
# TODO(morgan): Remove this test in a future patch.
|
||||
self.skipTest('Test is not Valid, v2.0 has been removed.')
|
||||
client = TestClient(self.public_app)
|
||||
resp = client.get('/v2.0/')
|
||||
self.assertEqual(http_client.OK, resp.status_int)
|
||||
@ -796,6 +803,8 @@ class VersionTestCase(unit.TestCase):
|
||||
self.assertEqual(expected, data)
|
||||
|
||||
def test_admin_version_v2(self):
|
||||
# TODO(morgan): Remove this test in a future patch.
|
||||
self.skipTest('Test is not Valid, v2.0 has been removed.')
|
||||
client = TestClient(self.admin_app)
|
||||
resp = client.get('/v2.0/')
|
||||
self.assertEqual(http_client.OK, resp.status_int)
|
||||
@ -806,6 +815,8 @@ class VersionTestCase(unit.TestCase):
|
||||
self.assertEqual(expected, data)
|
||||
|
||||
def test_use_site_url_if_endpoint_unset_v2(self):
|
||||
# TODO(morgan): Remove this test in a future patch.
|
||||
self.skipTest('Test is not Valid, v2.0 has been removed.')
|
||||
self.config_fixture.config(public_endpoint=None, admin_endpoint=None)
|
||||
for app in (self.public_app, self.admin_app):
|
||||
client = TestClient(app)
|
||||
@ -850,6 +861,9 @@ class VersionTestCase(unit.TestCase):
|
||||
|
||||
@mock.patch.object(controllers, '_VERSIONS', ['v3'])
|
||||
def test_v2_disabled(self):
|
||||
# NOTE(morgan): This test should be kept, v2.0 is removed and should
|
||||
# never return, this prevents regression[s]/v2.0 discovery doc
|
||||
# slipping back in.
|
||||
client = TestClient(self.public_app)
|
||||
# request to /v2.0 should fail
|
||||
resp = client.get('/v2.0/')
|
||||
@ -879,8 +893,9 @@ class VersionTestCase(unit.TestCase):
|
||||
data = jsonutils.loads(resp.body)
|
||||
self.assertEqual(v3_only_response, data)
|
||||
|
||||
@mock.patch.object(controllers, '_VERSIONS', ['v2.0'])
|
||||
def test_v3_disabled(self):
|
||||
# TODO(morgan): Remove this test in a future patch.
|
||||
self.skipTest('Test is not Valid, v2.0 has been removed.')
|
||||
client = TestClient(self.public_app)
|
||||
# request to /v3 should fail
|
||||
resp = client.get('/v3/')
|
||||
@ -1027,7 +1042,7 @@ class VersionSingleAppTestCase(unit.TestCase):
|
||||
return self.admin_port
|
||||
else:
|
||||
return self.public_port
|
||||
app = self.loadapp('keystone', app_name)
|
||||
app = self.loadapp(app_name)
|
||||
client = TestClient(app)
|
||||
resp = client.get('/')
|
||||
self.assertEqual(300, resp.status_int)
|
||||
@ -1038,12 +1053,14 @@ class VersionSingleAppTestCase(unit.TestCase):
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost:%s/v3/' % app_port())
|
||||
elif version['id'] == 'v2.0':
|
||||
# TODO(morgan): remove this if block in future patch,
|
||||
# v2.0 has been removed.
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost:%s/v2.0/' % app_port())
|
||||
self.assertThat(data, _VersionsEqual(expected))
|
||||
|
||||
def test_public(self):
|
||||
self._test_version('main')
|
||||
self._test_version('public')
|
||||
|
||||
def test_admin(self):
|
||||
self._test_version('admin')
|
||||
@ -1053,7 +1070,7 @@ class VersionBehindSslTestCase(unit.TestCase):
|
||||
def setUp(self):
|
||||
super(VersionBehindSslTestCase, self).setUp()
|
||||
self.load_backends()
|
||||
self.public_app = self.loadapp('keystone', 'main')
|
||||
self.public_app = self.loadapp('public')
|
||||
|
||||
def config_overrides(self):
|
||||
super(VersionBehindSslTestCase, self).config_overrides()
|
||||
|
@ -32,6 +32,8 @@ build_resource_relation = functools.partial(
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('OS-SIMPLE-CERT',)
|
||||
|
||||
def _construct_url(self, suffix):
|
||||
return "/OS-SIMPLE-CERT/%s" % suffix
|
||||
|
||||
|
@ -30,6 +30,8 @@ TRUST_ID_PARAMETER_RELATION = json_home.build_v3_extension_parameter_relation(
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('OS-TRUST',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
trust_controller = controllers.TrustV3()
|
||||
|
||||
|
@ -16,7 +16,6 @@ import functools
|
||||
import sys
|
||||
|
||||
from oslo_log import log
|
||||
from paste import deploy
|
||||
import routes
|
||||
|
||||
from keystone.application_credential import routers as app_cred_routers
|
||||
@ -34,6 +33,8 @@ from keystone.oauth1 import routers as oauth1_routers
|
||||
from keystone.policy import routers as policy_routers
|
||||
from keystone.resource import routers as resource_routers
|
||||
from keystone.revoke import routers as revoke_routers
|
||||
from keystone.server import flask as keystone_flask
|
||||
from keystone.server.flask import application
|
||||
from keystone.token import _simple_cert as simple_cert_ext
|
||||
from keystone.trust import routers as trust_routers
|
||||
from keystone.version import controllers
|
||||
@ -44,11 +45,12 @@ CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def loadapp(conf, name):
|
||||
def loadapp(name):
|
||||
# NOTE(blk-u): Save the application being loaded in the controllers module.
|
||||
# This is similar to how public_app_factory() and v3_app_factory()
|
||||
# register the version with the controllers module.
|
||||
controllers.latest_app = deploy.loadapp(conf, name=name)
|
||||
controllers.latest_app = keystone_flask.setup_app_middleware(
|
||||
application.application_factory(name))
|
||||
return controllers.latest_app
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@ fasteners==0.14.1
|
||||
fixtures==3.0.0
|
||||
flake8-docstrings==0.2.1.post1
|
||||
flake8==2.5.5
|
||||
Flask===0.10
|
||||
freezegun==0.3.6
|
||||
future==0.16.0
|
||||
futurist==1.6.0
|
||||
|
@ -11,6 +11,7 @@ WebOb>=1.7.1 # MIT
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
Paste>=2.0.2 # MIT
|
||||
Routes>=2.3.1 # MIT
|
||||
Flask!=0.11,<1.0 # BSD
|
||||
cryptography>=2.1 # BSD/Apache-2.0
|
||||
six>=1.10.0 # MIT
|
||||
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
|
||||
|
15
setup.cfg
15
setup.cfg
@ -183,7 +183,21 @@ oslo.policy.policies =
|
||||
oslo.policy.enforcer =
|
||||
keystone = keystone.common.policy:get_enforcer
|
||||
|
||||
keystone.server_middleware =
|
||||
healthcheck = oslo_middleware:Healthcheck
|
||||
cors = oslo_middleware:CORS
|
||||
sizelimit = oslo_middleware:RequestBodySizeLimiter
|
||||
http_proxy_to_wsgi = oslo_middleware:HTTPProxyToWSGI
|
||||
osprofiler = osprofiler.web:WsgiMiddleware
|
||||
url_normalize = keystone.middleware:NormalizingFilter
|
||||
request_id = oslo_middleware:RequestId
|
||||
build_auth_context = keystone.middleware:AuthContextMiddleware
|
||||
token_auth = keystone.middleware:TokenAuthMiddleware
|
||||
json_body = keystone.middleware:JsonBodyMiddleware
|
||||
debug = oslo_middleware:Debug
|
||||
|
||||
paste.filter_factory =
|
||||
# TODO(morgan): Remove paste.filter_factory
|
||||
healthcheck = oslo_middleware:Healthcheck.factory
|
||||
cors = oslo_middleware:CORS.factory
|
||||
sizelimit = oslo_middleware:RequestBodySizeLimiter.factory
|
||||
@ -200,6 +214,7 @@ paste.filter_factory =
|
||||
s3_extension = keystone.contrib.s3:S3Extension.factory
|
||||
|
||||
paste.app_factory =
|
||||
# TODO(morgan): Remove paste.app_factory
|
||||
admin_service = keystone.version.service:admin_app_factory
|
||||
admin_version_service = keystone.version.service:admin_version_app_factory
|
||||
public_service = keystone.version.service:public_app_factory
|
||||
|
Loading…
x
Reference in New Issue
Block a user