Make versions aware of enabled pipelines.

Updates to make our versions controller a bit smarter so
that it only returns information on API versions which are
actually running.

With these changes a user can disable the v2.0 or v3 API
versions in their pipeline, restart keystone, and then have
versions return information only for the versions which
are actually running.

This is important because auth_token now uses info from the
keystone versions controller (in some cases) to dynamically
select an API version.

Fixes LP Bug #1158470.
Change-Id: I0fa8a82f08e7247c44fb7f4ff8dbb7d4ad58b9cc
This commit is contained in:
Dan Prince 2013-03-14 13:29:47 -04:00
parent ba3f41f068
commit 620e6e3780
4 changed files with 143 additions and 58 deletions

View File

@ -19,13 +19,14 @@ from keystone.common import logging
from keystone import config
from keystone import exception
LOG = logging.getLogger(__name__)
CONF = config.CONF
MEDIA_TYPE_JSON = 'application/vnd.openstack.identity-%s+json'
MEDIA_TYPE_XML = 'application/vnd.openstack.identity-%s+xml'
_VERSIONS = []
class Extensions(wsgi.Application):
"""Base extensions controller to be extended by public and admin API's."""
@ -76,7 +77,12 @@ class PublicExtensions(Extensions):
pass
def register_version(version):
_VERSIONS.append(version)
class Version(wsgi.Application):
def __init__(self, version_type):
self.endpoint_url_type = version_type
@ -92,57 +98,60 @@ class Version(wsgi.Application):
def _get_versions_list(self, context):
"""The list of versions is dependent on the context."""
versions = {}
versions['v2.0'] = {
'id': 'v2.0',
'status': 'stable',
'updated': '2013-03-06T00:00:00Z',
'links': [
{
'rel': 'self',
'href': self._get_identity_url(version='v2.0'),
}, {
'rel': 'describedby',
'type': 'text/html',
'href': 'http://docs.openstack.org/api/openstack-'
'identity-service/2.0/content/'
}, {
'rel': 'describedby',
'type': 'application/pdf',
'href': 'http://docs.openstack.org/api/openstack-'
'identity-service/2.0/identity-dev-guide-'
'2.0.pdf'
}
],
'media-types': [
{
'base': 'application/json',
'type': MEDIA_TYPE_JSON % 'v2.0'
}, {
'base': 'application/xml',
'type': MEDIA_TYPE_XML % 'v2.0'
}
]
}
versions['v3'] = {
'id': 'v3.0',
'status': 'stable',
'updated': '2013-03-06T00:00:00Z',
'links': [
{
'rel': 'self',
'href': self._get_identity_url(version='v3'),
}
],
'media-types': [
{
'base': 'application/json',
'type': MEDIA_TYPE_JSON % 'v3'
}, {
'base': 'application/xml',
'type': MEDIA_TYPE_XML % 'v3'
}
]
}
if 'v2.0' in _VERSIONS:
versions['v2.0'] = {
'id': 'v2.0',
'status': 'stable',
'updated': '2013-03-06T00:00:00Z',
'links': [
{
'rel': 'self',
'href': self._get_identity_url(version='v2.0'),
}, {
'rel': 'describedby',
'type': 'text/html',
'href': 'http://docs.openstack.org/api/openstack-'
'identity-service/2.0/content/'
}, {
'rel': 'describedby',
'type': 'application/pdf',
'href': 'http://docs.openstack.org/api/openstack-'
'identity-service/2.0/identity-dev-guide-'
'2.0.pdf'
}
],
'media-types': [
{
'base': 'application/json',
'type': MEDIA_TYPE_JSON % 'v2.0'
}, {
'base': 'application/xml',
'type': MEDIA_TYPE_XML % 'v2.0'
}
]
}
if 'v3' in _VERSIONS:
versions['v3'] = {
'id': 'v3.0',
'status': 'stable',
'updated': '2013-03-06T00:00:00Z',
'links': [
{
'rel': 'self',
'href': self._get_identity_url(version='v3'),
}
],
'media-types': [
{
'base': 'application/json',
'type': MEDIA_TYPE_JSON % 'v3'
}, {
'base': 'application/xml',
'type': MEDIA_TYPE_XML % 'v3'
}
]
}
return versions
@ -156,12 +165,18 @@ class Version(wsgi.Application):
def get_version_v2(self, context):
versions = self._get_versions_list(context)
return wsgi.render_response(body={
'version': versions['v2.0']
})
if 'v2.0' in _VERSIONS:
return wsgi.render_response(body={
'version': versions['v2.0']
})
else:
raise exception.VersionNotFound(version='v2.0')
def get_version_v3(self, context):
versions = self._get_versions_list(context)
return wsgi.render_response(body={
'version': versions['v3']
})
if 'v3' in _VERSIONS:
return wsgi.render_response(body={
'version': versions['v3']
})
else:
raise exception.VersionNotFound(version='v3')

View File

@ -204,6 +204,10 @@ class CredentialNotFound(NotFound):
"""Could not find credential: %(credential_id)s"""
class VersionNotFound(NotFound):
"""Could not find version: %(version)s"""
class Conflict(Error):
"""Conflict occurred attempting to store %(type)s.

View File

@ -19,6 +19,7 @@ import routes
from keystone import auth
from keystone import catalog
from keystone import config
from keystone import controllers
from keystone.common import logging
from keystone.common import wsgi
from keystone.contrib import ec2
@ -43,6 +44,7 @@ DRIVERS = dict(
@logging.fail_gracefully
def public_app_factory(global_conf, **local_conf):
controllers.register_version('v2.0')
conf = global_conf.copy()
conf.update(local_conf)
return wsgi.ComposingRouter(routes.Mapper(),
@ -81,6 +83,7 @@ def admin_version_app_factory(global_conf, **local_conf):
@logging.fail_gracefully
def v3_app_factory(global_conf, **local_conf):
controllers.register_version('v3')
conf = global_conf.copy()
conf.update(local_conf)
mapper = routes.Mapper()

View File

@ -16,6 +16,7 @@
# limitations under the License.
from keystone import config
from keystone import controllers
from keystone.openstack.common import jsonutils
from keystone import test
@ -193,3 +194,65 @@ class VersionTestCase(test.TestCase):
self._paste_in_port(expected['version'],
'http://localhost:%s/v3/' % CONF.admin_port)
self.assertEqual(data, expected)
def test_v2_disabled(self):
self.stubs.Set(controllers, '_VERSIONS', ['v3'])
client = self.client(self.public_app)
# request to /v2.0 should fail
resp = client.get('/v2.0/')
self.assertEqual(resp.status_int, 404)
# request to /v3 should pass
resp = client.get('/v3/')
self.assertEqual(resp.status_int, 200)
data = jsonutils.loads(resp.body)
expected = v3_VERSION_RESPONSE
self._paste_in_port(expected['version'],
'http://localhost:%s/v3/' % CONF.public_port)
self.assertEqual(data, expected)
# only v3 information should be displayed by requests to /
v3_only_response = {
"versions": {
"values": [
v3_EXPECTED_RESPONSE
]
}
}
self._paste_in_port(v3_only_response['versions']['values'][0],
'http://localhost:%s/v3/' % CONF.public_port)
resp = client.get('/')
self.assertEqual(resp.status_int, 300)
data = jsonutils.loads(resp.body)
self.assertEqual(data, v3_only_response)
def test_v3_disabled(self):
self.stubs.Set(controllers, '_VERSIONS', ['v2.0'])
client = self.client(self.public_app)
# request to /v3 should fail
resp = client.get('/v3/')
self.assertEqual(resp.status_int, 404)
# request to /v2.0 should pass
resp = client.get('/v2.0/')
self.assertEqual(resp.status_int, 200)
data = jsonutils.loads(resp.body)
expected = v2_VERSION_RESPONSE
self._paste_in_port(expected['version'],
'http://localhost:%s/v2.0/' % CONF.public_port)
self.assertEqual(data, expected)
# only v2 information should be displayed by requests to /
v2_only_response = {
"versions": {
"values": [
v2_EXPECTED_RESPONSE
]
}
}
self._paste_in_port(v2_only_response['versions']['values'][0],
'http://localhost:%s/v2.0/' % CONF.public_port)
resp = client.get('/')
self.assertEqual(resp.status_int, 300)
data = jsonutils.loads(resp.body)
self.assertEqual(data, v2_only_response)