Convert json_home and version discovery to Flask

Move the JSON Home Document and Version Discovery Documents out of
the webob-based mapper and into Flask.

This change removes the keystone.version.controller and
keystone.version.router modules as they have been moved into
keystone.api.discovery.

The keystone.api.discovery module is somewhat specialized as there
are no "resources" and it must handle multiple types of responses
based upon the ACCEPTS header (JSON Home or JSON). In lieu of the
flask-RESTful mechanisms, keystone.api.discovery utilizes bare
flask blueprint and functions. Minor scaffolding work has been done
to ensure the discovery blueprint can be loaded via the loader loop
in keystone.server.flask.application (a stub object in
keystone.api.discovery).

Partial-Bug: #1776504
Change-Id: Ib25380cefdbb7147661bb9853de7872a837322e0
This commit is contained in:
Morgan Fainberg 2018-06-12 06:42:21 -07:00
parent ecf721a3c1
commit 3e3ba18bfa
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 = [
@ -746,7 +746,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
@ -822,8 +822,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)
@ -852,17 +852,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