diff --git a/keystone/controllers.py b/keystone/controllers.py index e19840e988..8864ca6c98 100644 --- a/keystone/controllers.py +++ b/keystone/controllers.py @@ -23,6 +23,9 @@ 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' + class Extensions(wsgi.Application): """Base extensions controller to be extended by public and admin API's.""" @@ -113,12 +116,30 @@ class Version(wsgi.Application): 'media-types': [ { 'base': 'application/json', - 'type': 'application/vnd.openstack.identity-v2.0' - '+json' + 'type': MEDIA_TYPE_JSON % 'v2.0' }, { 'base': 'application/xml', - 'type': 'application/vnd.openstack.identity-v2.0' - '+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' } ] } @@ -133,8 +154,14 @@ class Version(wsgi.Application): } }) - def get_version(self, context): + def get_version_v2(self, context): versions = self._get_versions_list(context) return wsgi.render_response(body={ 'version': versions['v2.0'] }) + + def get_version_v3(self, context): + versions = self._get_versions_list(context) + return wsgi.render_response(body={ + 'version': versions['v3'] + }) diff --git a/keystone/routers.py b/keystone/routers.py index 83f277dd26..f1a380e7e7 100644 --- a/keystone/routers.py +++ b/keystone/routers.py @@ -47,7 +47,7 @@ class Extension(wsgi.ComposableRouter): conditions=dict(method=['GET'])) -class Version(wsgi.ComposableRouter): +class VersionV2(wsgi.ComposableRouter): def __init__(self, description): self.description = description @@ -55,7 +55,18 @@ class Version(wsgi.ComposableRouter): version_controller = controllers.Version(self.description) mapper.connect('/', controller=version_controller, - action='get_version') + action='get_version_v2') + + +class VersionV3(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_v3') class Versions(wsgi.ComposableRouter): diff --git a/keystone/service.py b/keystone/service.py index 423aee066a..6c7587b261 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -46,7 +46,7 @@ def public_app_factory(global_conf, **local_conf): return wsgi.ComposingRouter(routes.Mapper(), [identity.routers.Public(), token.routers.Router(), - routers.Version('public'), + routers.VersionV2('public'), routers.Extension(False)]) @@ -57,7 +57,7 @@ def admin_app_factory(global_conf, **local_conf): return wsgi.ComposingRouter(routes.Mapper(), [identity.routers.Admin(), token.routers.Router(), - routers.Version('admin'), + routers.VersionV2('admin'), routers.Extension()]) @@ -85,5 +85,8 @@ def v3_app_factory(global_conf, **local_conf): v3routers = [] for module in [auth, catalog, identity, policy, trust]: module.routers.append_v3_routers(mapper, v3routers) + # Add in the v3 version api + v3routers.append(routers.VersionV3('admin')) + v3routers.append(routers.VersionV3('public')) # TODO(ayoung): put token routes here return wsgi.ComposingRouter(mapper, v3routers) diff --git a/tests/test_versions.py b/tests/test_versions.py index 8faf68cad5..0ca2edda49 100644 --- a/tests/test_versions.py +++ b/tests/test_versions.py @@ -22,6 +22,91 @@ from keystone import test CONF = config.CONF +v2_MEDIA_TYPES = [ + { + "base": "application/json", + "type": "application/" + "vnd.openstack.identity-v2.0+json" + }, { + "base": "application/xml", + "type": "application/" + "vnd.openstack.identity-v2.0+xml" + } +] + +v2_HTML_DESCRIPTION = { + "rel": "describedby", + "type": "text/html", + "href": "http://docs.openstack.org/api/" + "openstack-identity-service/2.0/" + "content/" +} + +v2_PDF_DESCRIPTION = { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.openstack.org/api/" + "openstack-identity-service/2.0/" + "identity-dev-guide-2.0.pdf" +} + +v2_EXPECTED_RESPONSE = { + "id": "v2.0", + "status": "stable", + "updated": "2013-03-06T00:00:00Z", + "links": [ + { + "rel": "self", + "href": "", # Will get filled in after initialization + }, + v2_HTML_DESCRIPTION, + v2_PDF_DESCRIPTION + ], + "media-types": v2_MEDIA_TYPES +} + +v2_VERSION_RESPONSE = { + "version": v2_EXPECTED_RESPONSE +} + +v3_MEDIA_TYPES = [ + { + "base": "application/json", + "type": "application/" + "vnd.openstack.identity-v3+json" + }, { + "base": "application/xml", + "type": "application/" + "vnd.openstack.identity-v3+xml" + } +] + +v3_EXPECTED_RESPONSE = { + "id": "v3.0", + "status": "stable", + "updated": "2013-03-06T00:00:00Z", + "links": [ + { + "rel": "self", + "href": "", # Will get filled in after initialization + } + ], + "media-types": v3_MEDIA_TYPES +} + +v3_VERSION_RESPONSE = { + "version": v3_EXPECTED_RESPONSE +} + +VERSIONS_RESPONSE = { + "versions": { + "values": [ + v3_EXPECTED_RESPONSE, + v2_EXPECTED_RESPONSE + ] + } +} + class VersionTestCase(test.TestCase): def setUp(self): @@ -33,52 +118,24 @@ class VersionTestCase(test.TestCase): self.public_server = self.serveapp('keystone', name='main') self.admin_server = self.serveapp('keystone', name='admin') + def _paste_in_port(self, response, port): + for link in response['links']: + if link['rel'] == 'self': + link['href'] = port + def test_public_versions(self): client = self.client(self.public_app) resp = client.get('/') self.assertEqual(resp.status_int, 300) data = jsonutils.loads(resp.body) - expected = { - "versions": { - "values": [ - { - "id": "v2.0", - "status": "stable", - "updated": '2013-03-06T00:00:00Z', - "links": [ - { - "rel": "self", - "href": "http://localhost:%s/v2.0/" % - CONF.public_port, - }, { - "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": "application/" - "vnd.openstack.identity-v2.0+json" - }, { - "base": "application/xml", - "type": "application/" - "vnd.openstack.identity-v2.0+xml" - } - ] - } - ] - } - } + expected = VERSIONS_RESPONSE + for version in expected['versions']['values']: + if version['id'] == 'v3.0': + self._paste_in_port( + version, 'http://localhost:%s/v3/' % CONF.public_port) + elif version['id'] == 'v2.0': + self._paste_in_port( + version, 'http://localhost:%s/v2.0/' % CONF.public_port) self.assertEqual(data, expected) def test_admin_versions(self): @@ -86,45 +143,53 @@ class VersionTestCase(test.TestCase): resp = client.get('/') self.assertEqual(resp.status_int, 300) data = jsonutils.loads(resp.body) - expected = { - "versions": { - "values": [ - { - "id": "v2.0", - "status": "stable", - "updated": '2013-03-06T00:00:00Z', - "links": [ - { - "rel": "self", - "href": "http://localhost:%s/v2.0/" % - CONF.admin_port, - }, { - "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": "application/" - "vnd.openstack.identity-v2.0+json" - }, { - "base": "application/xml", - "type": "application/" - "vnd.openstack.identity-v2.0+xml" - } - ] - } - ] - } - } + expected = VERSIONS_RESPONSE + for version in expected['versions']['values']: + if version['id'] == 'v3.0': + self._paste_in_port( + version, 'http://localhost:%s/v3/' % CONF.admin_port) + elif version['id'] == 'v2.0': + self._paste_in_port( + version, 'http://localhost:%s/v2.0/' % CONF.admin_port) + self.assertEqual(data, expected) + + def test_public_version_v2(self): + client = self.client(self.public_app) + 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) + + def test_admin_version_v2(self): + client = self.client(self.admin_app) + 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.admin_port) + self.assertEqual(data, expected) + + def test_public_version_v3(self): + print CONF.public_port + client = self.client(self.public_app) + 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) + + def test_admin_version_v3(self): + client = self.client(self.public_app) + 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.admin_port) self.assertEqual(data, expected)