Merge "Convert json_home and version discovery to Flask"

This commit is contained in:
Zuul 2018-07-12 15:45:52 +00:00 committed by Gerrit Code Review
commit 1efa27e4ed
12 changed files with 249 additions and 403 deletions

16
keystone/api/__init__.py Normal file
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.api import discovery
__all__ = ('discovery',)
__apis__ = (discovery,)

142
keystone/api/discovery.py Normal file
View File

@ -0,0 +1,142 @@
# 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 flask
from flask import request
from oslo_serialization import jsonutils
from six.moves import http_client
from keystone.common import json_home
from keystone.common import wsgi
import keystone.conf
from keystone import exception
CONF = keystone.conf.CONF
MEDIA_TYPE_JSON = 'application/vnd.openstack.identity-%s+json'
_VERSIONS = []
_DISCOVERY_BLUEPRINT = flask.Blueprint('Discovery', __name__)
def register_version(version):
_VERSIONS.append(version)
def _get_versions_list(identity_url):
versions = {}
if 'v3' in _VERSIONS:
versions['v3'] = {
'id': 'v3.10',
'status': 'stable',
'updated': '2018-02-28T00:00:00Z',
'links': [
{
'rel': 'self',
'href': identity_url,
}
],
'media-types': [
{
'base': 'application/json',
'type': MEDIA_TYPE_JSON % 'v3'
}
]
}
return versions
class MimeTypes(object):
JSON = 'application/json'
JSON_HOME = 'application/json-home'
def _v3_json_home_content():
# TODO(morgan): Eliminate this, we should never be disabling an API version
# now, JSON Home should never be empty.
if 'v3' not in _VERSIONS:
# No V3 Support, so return an empty JSON Home document.
return {'resources': {}}
return json_home.JsonHomeResources.resources()
def v3_mime_type_best_match():
if not request.accept_mimetypes:
return MimeTypes.JSON
return request.accept_mimetypes.best_match(
[MimeTypes.JSON, MimeTypes.JSON_HOME])
@_DISCOVERY_BLUEPRINT.route('/')
def get_versions():
if v3_mime_type_best_match() == MimeTypes.JSON_HOME:
# RENDER JSON-Home form, we have a clever client who will
# understand the JSON-Home document.
v3_json_home = _v3_json_home_content()
json_home.translate_urls(v3_json_home, '/v3')
return flask.Response(response=jsonutils.dumps(v3_json_home),
mimetype=MimeTypes.JSON_HOME)
else:
# NOTE(morgan): wsgi.Application.base_url will eventually need to
# be moved to a better "common" location. For now, we'll just lean
# on it for the sake of leaning on common code where possible.
identity_url = '%s/v3/' % wsgi.Application.base_url(
context={'environment': request.environ})
versions = _get_versions_list(identity_url)
return flask.Response(
response=jsonutils.dumps(
{'versions': {
'values': list(versions.values())}}),
mimetype=MimeTypes.JSON,
status=http_client.MULTIPLE_CHOICES)
@_DISCOVERY_BLUEPRINT.route('/v3')
def get_version_v3():
if 'v3' not in _VERSIONS:
raise exception.VersionNotFound(version='v3')
if v3_mime_type_best_match() == MimeTypes.JSON_HOME:
# RENDER JSON-Home form, we have a clever client who will
# understand the JSON-Home document.
content = _v3_json_home_content()
return flask.Response(response=jsonutils.dumps(content),
mimetype=MimeTypes.JSON_HOME)
else:
# NOTE(morgan): wsgi.Application.base_url will eventually need to
# be moved to a better "common" location. For now, we'll just lean
# on it for the sake of leaning on common code where possible.
identity_url = '%s/v3/' % wsgi.Application.base_url(
context={'environment': request.environ})
versions = _get_versions_list(identity_url)
return flask.Response(
response=jsonutils.dumps({'version': versions['v3']}),
mimetype=MimeTypes.JSON)
class DiscoveryAPI(object):
# NOTE(morgan): The Discovery Bits are so special they cannot conform to
# Flask-RESTful-isms. We are using straight flask Blueprint(s) here so that
# we have a lot more control over what the heck is going on. This is just
# a stub object to ensure we can load discovery in the same manner we
# handle the rest of keystone.api
@staticmethod
def instantiate_and_register_to_app(flask_app):
# This is a lot more magical than the normal setup as the discovery
# API does not lean on flask-restful. We're statically building a
# single blueprint here.
flask_app.register_blueprint(_DISCOVERY_BLUEPRINT)
APIs = (DiscoveryAPI,)

View File

@ -72,7 +72,7 @@ class Routers(wsgi.RoutersBase):
PATH_ENDPOINT_GROUP_PROJECTS = PATH_ENDPOINT_GROUPS + (
'/projects/{project_id}')
_path_prefixes = (PATH_PREFIX, 'regions', 'endpoints', 'services')
_path_prefixes = ('OS-EP-FILTER', 'regions', 'endpoints', 'services')
def append_v3_routers(self, mapper, routers):
regions_controller = controllers.RegionV3()

View File

@ -23,7 +23,7 @@ from oslo_middleware import healthcheck
import routes
import werkzeug.wsgi
import keystone.api
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
@ -42,8 +42,6 @@ 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__)
@ -105,72 +103,53 @@ class KeystoneDispatcherMiddleware(werkzeug.wsgi.DispatcherMiddleware):
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 = ''
# 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.
#
# 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.')
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
environ['PATH_INFO'] = path_info
# 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:
# 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'] = '/'
if app != self.app:
LOG.debug('Dispatching (fallthrough) request to legacy '
'mapper: %s', script)
else:
LOG.debug('Dispatching to flask native app for version '
'discovery document: `%s`', script)
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
@ -183,6 +162,18 @@ class KeystoneDispatcherMiddleware(werkzeug.wsgi.DispatcherMiddleware):
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):
# Add the expected Vary Header, this is run after every request in the
# response-phase
response.headers['Vary'] = 'X-Auth-Token'
return response
@fail_gracefully
def application_factory(name='public'):
if name not in ('admin', 'public'):
@ -192,6 +183,12 @@ def application_factory(name='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.after_request(_add_vary_x_auth_token_header)
# NOTE(morgan): Configure the Flask Environment for our needs.
app.config.update(
# We want to bubble up Flask Exceptions (for now)
PROPAGATE_EXCEPTIONS=True)
# TODO(morgan): Convert Subsystems over to Flask-Native, for now, we simply
# dispatch to another "application" [e.g "keystone"]
@ -215,29 +212,24 @@ def application_factory(name='public'):
_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')
# TODO(morgan): Remove "API version registration". For now this is kept
# for ease of conversion (minimal changes)
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_bp in api.APIs:
api_bp.instantiate_and_register_to_app(app)
# Build and construct the dispatching for the Legacy dispatching model
sub_routers.append(_ComposibleRouterStub(_routers))
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)

View File

@ -119,4 +119,4 @@ class APIBase(object):
explicitly via normal instantiation where more values may be passed
via :meth:`__init__`.
"""
flask_app.register(cls())
flask_app.register_blueprint(cls().blueprint)

View File

@ -16,6 +16,7 @@ import os
import oslo_i18n
from oslo_log import log
import stevedore
from werkzeug.contrib import fixers
# NOTE(dstanek): i18n.enable_lazy() must be called before
@ -85,7 +86,7 @@ def _get_config_files(env=None):
return files
def setup_app_middleware(application):
def setup_app_middleware(app):
# 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.
@ -121,8 +122,11 @@ def setup_app_middleware(application):
# 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
app = factory_func(app)
# Apply werkzeug speficic middleware
app = fixers.ProxyFix(app)
return app
def initialize_application(name, post_log_configured_function=lambda: None,

View File

@ -39,6 +39,7 @@ from sqlalchemy import exc
import testtools
from testtools import testcase
import keystone.api
from keystone.common import context
from keystone.common import json_home
from keystone.common import provider_api
@ -49,9 +50,8 @@ from keystone import exception
from keystone.identity.backends.ldap import common as ks_ldap
from keystone import notifications
from keystone.resource.backends import base as resource_base
from keystone.server import flask as keystone_flask
from keystone.tests.unit import ksfixtures
from keystone.version import controllers
from keystone.version import service
keystone.conf.configure()
@ -693,7 +693,7 @@ class TestCase(BaseTestCase):
self.addCleanup(notifications.clear_subscribers)
self.addCleanup(notifications.reset_notifier)
self.addCleanup(setattr, controllers, '_VERSIONS', [])
self.addCleanup(setattr, keystone.api.discovery, '_VERSIONS', [])
def config(self, config_files):
sql.initialize()
@ -782,7 +782,9 @@ class TestCase(BaseTestCase):
self.addCleanup(self.cleanup_instance(*fixtures_to_cleanup))
def loadapp(self, name='public'):
return service.loadapp(name=name)
app = keystone_flask.application.application_factory(name)
app.config.update(PROPAGATE_EXCEPTIONS=True, testing=True)
return keystone_flask.setup_app_middleware(app)
def assertCloseEnoughForGovernmentWork(self, a, b, delta=3):
"""Assert that two datetimes are nearly equal within a small delta.

View File

@ -23,9 +23,9 @@ from six.moves import http_client
from testtools import matchers as tt_matchers
import webob
from keystone.api import discovery
from keystone.common import json_home
from keystone.tests import unit
from keystone.version import controllers
v3_MEDIA_TYPES = [
@ -750,7 +750,7 @@ class VersionTestCase(unit.TestCase):
self._paste_in_port(expected['version'], 'http://localhost/v3/')
self.assertEqual(expected, data)
@mock.patch.object(controllers, '_VERSIONS', ['v3'])
@mock.patch.object(discovery, '_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
@ -826,8 +826,8 @@ class VersionTestCase(unit.TestCase):
self.assertThat(resp.status, tt_matchers.Equals('200 OK'))
return resp.headers['Content-Type']
JSON = controllers.MimeTypes.JSON
JSON_HOME = controllers.MimeTypes.JSON_HOME
JSON = discovery.MimeTypes.JSON
JSON_HOME = discovery.MimeTypes.JSON_HOME
JSON_MATCHER = tt_matchers.Equals(JSON)
JSON_HOME_MATCHER = tt_matchers.Equals(JSON_HOME)
@ -856,17 +856,13 @@ class VersionTestCase(unit.TestCase):
# If request some unknown mime-type, get JSON.
self.assertThat(make_request(self.getUniqueString()), JSON_MATCHER)
@mock.patch.object(controllers, '_VERSIONS', [])
@mock.patch.object(discovery, '_VERSIONS', [])
def test_no_json_home_document_returned_when_v3_disabled(self):
json_home_document = controllers.request_v3_json_home('some_prefix')
json_home_document = discovery._v3_json_home_content()
json_home.translate_urls(json_home_document, '/v3')
expected_document = {'resources': {}}
self.assertEqual(expected_document, json_home_document)
def test_extension_property_method_returns_none(self):
extension_obj = controllers.Extensions()
extensions_property = extension_obj.extensions
self.assertIsNone(extensions_property)
class VersionSingleAppTestCase(unit.TestCase):
"""Test running with a single application loaded.

View File

@ -1,193 +0,0 @@
# Copyright 2012 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.
from six.moves import http_client
from keystone.common import extension
from keystone.common import json_home
from keystone.common import wsgi
from keystone import exception
MEDIA_TYPE_JSON = 'application/vnd.openstack.identity-%s+json'
_VERSIONS = []
# NOTE(blk-u): latest_app will be set by keystone.version.service.loadapp(). It
# gets set to the application that was just loaded. loadapp() gets called once
# for the public app if this is the public instance or loadapp() gets called
# for the admin app if it's the admin instance. This is used to fetch the /v3
# JSON Home response. The /v3 JSON Home response is the same whether it's the
# admin or public service so either admin or public works.
latest_app = None
def request_v3_json_home(new_prefix):
if 'v3' not in _VERSIONS:
# No V3 support, so return an empty JSON Home document.
return {'resources': {}}
v3_json_home = json_home.JsonHomeResources.resources()
json_home.translate_urls(v3_json_home, new_prefix)
return v3_json_home
class Extensions(wsgi.Application):
"""Base extensions controller to be extended by public and admin API's."""
# extend in subclass to specify the set of extensions
@property
def extensions(self):
return None
def get_extensions_info(self, request):
return {'extensions': {'values': list(self.extensions.values())}}
def get_extension_info(self, request, extension_alias):
try:
return {'extension': self.extensions[extension_alias]}
except KeyError:
raise exception.NotFound(target=extension_alias)
class AdminExtensions(Extensions):
@property
def extensions(self):
return extension.ADMIN_EXTENSIONS
class PublicExtensions(Extensions):
@property
def extensions(self):
return extension.PUBLIC_EXTENSIONS
def register_version(version):
_VERSIONS.append(version)
class MimeTypes(object):
JSON = 'application/json'
JSON_HOME = 'application/json-home'
def v3_mime_type_best_match(request):
# accept_header is a WebOb MIMEAccept object so supports best_match.
accept_header = request.accept
if not accept_header:
return MimeTypes.JSON
SUPPORTED_TYPES = [MimeTypes.JSON, MimeTypes.JSON_HOME]
return accept_header.best_match(SUPPORTED_TYPES)
class Version(wsgi.Application):
def __init__(self, version_type, routers=None):
self.endpoint_url_type = version_type
self._routers = routers
super(Version, self).__init__()
def _get_identity_url(self, context, version):
"""Return a URL to keystone's own endpoint."""
url = self.base_url(context, self.endpoint_url_type)
return '%s/%s/' % (url, version)
def _get_versions_list(self, context):
"""The list of versions is dependent on the context."""
versions = {}
if 'v2.0' in _VERSIONS:
versions['v2.0'] = {
'id': 'v2.0',
'status': 'deprecated',
'updated': '2016-08-04T00:00:00Z',
'links': [
{
'rel': 'self',
'href': self._get_identity_url(context, 'v2.0'),
}, {
'rel': 'describedby',
'type': 'text/html',
'href': 'https://docs.openstack.org/'
}
],
'media-types': [
{
'base': 'application/json',
'type': MEDIA_TYPE_JSON % 'v2.0'
}
]
}
if 'v3' in _VERSIONS:
versions['v3'] = {
'id': 'v3.10',
'status': 'stable',
'updated': '2018-02-28T00:00:00Z',
'links': [
{
'rel': 'self',
'href': self._get_identity_url(context, 'v3'),
}
],
'media-types': [
{
'base': 'application/json',
'type': MEDIA_TYPE_JSON % 'v3'
}
]
}
return versions
def get_versions(self, request):
req_mime_type = v3_mime_type_best_match(request)
if req_mime_type == MimeTypes.JSON_HOME:
v3_json_home = request_v3_json_home('/v3')
return wsgi.render_response(
body=v3_json_home,
headers=(('Content-Type', MimeTypes.JSON_HOME),))
versions = self._get_versions_list(request.context_dict)
return wsgi.render_response(
status=(http_client.MULTIPLE_CHOICES,
http_client.responses[http_client.MULTIPLE_CHOICES]),
body={
'versions': {
'values': list(versions.values())
}
})
def _get_json_home_v3(self):
return json_home.JsonHomeResources.resources()
def get_version_v3(self, request):
versions = self._get_versions_list(request.context_dict)
if 'v3' in _VERSIONS:
req_mime_type = v3_mime_type_best_match(request)
if req_mime_type == MimeTypes.JSON_HOME:
return wsgi.render_response(
body=self._get_json_home_v3(),
headers=(('Content-Type', MimeTypes.JSON_HOME),))
return wsgi.render_response(body={
'version': versions['v3']
})
else:
raise exception.VersionNotFound(version='v3')

View File

@ -1,80 +0,0 @@
# Copyright 2012 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.
"""
The only types of routers in this file should be ``ComposingRouters``.
The routers for the backends should be in the backend-specific router modules.
For example, the ``ComposableRouter`` for ``identity`` belongs in::
keystone.identity.routers
"""
from keystone.common import wsgi
from keystone.version import controllers
class Extension(wsgi.ComposableRouter):
def __init__(self, is_admin=True):
if is_admin:
self.controller = controllers.AdminExtensions()
else:
self.controller = controllers.PublicExtensions()
def add_routes(self, mapper):
extensions_controller = self.controller
mapper.connect('/extensions',
controller=extensions_controller,
action='get_extensions_info',
conditions=dict(method=['GET']))
mapper.connect('/extensions/{extension_alias}',
controller=extensions_controller,
action='get_extension_info',
conditions=dict(method=['GET']))
class VersionV2(wsgi.ComposableRouter):
def __init__(self, description):
self.description = description
def add_routes(self, mapper):
version_controller = controllers.Version(self.description)
mapper.connect('/',
controller=version_controller,
action='get_version_v2')
class VersionV3(wsgi.ComposableRouter):
def __init__(self, description, routers):
self.description = description
self._routers = routers
def add_routes(self, mapper):
version_controller = controllers.Version(self.description,
routers=self._routers)
mapper.connect('/',
controller=version_controller,
action='get_version_v3')
class Versions(wsgi.ComposableRouter):
def __init__(self, description):
self.description = description
def add_routes(self, mapper):
version_controller = controllers.Version(self.description)
mapper.connect('/',
controller=version_controller,
action='get_versions')

View File

@ -1,33 +0,0 @@
# Copyright 2012 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.
from oslo_log import log
import keystone.conf
from keystone.server import flask as keystone_flask
from keystone.server.flask import application
from keystone.version import controllers
CONF = keystone.conf.CONF
LOG = log.getLogger(__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 = keystone_flask.setup_app_middleware(
application.application_factory(name))
return controllers.latest_app