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:
Morgan Fainberg 2018-05-31 11:30:24 -07:00
parent 104717d458
commit 4ec6bc5a44
36 changed files with 548 additions and 30 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

@ -105,6 +105,8 @@ class Routers(wsgi.RoutersBase):
"""
_path_prefixes = ('auth', 'OS-FEDERATION')
def _construct_url(self, suffix):
return "/OS-FEDERATION/%s" % suffix

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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