diff --git a/bin/keystone-all b/bin/keystone-all index bf6431e5f7..8f31e01b4b 100755 --- a/bin/keystone-all +++ b/bin/keystone-all @@ -20,7 +20,6 @@ import socket import sys from oslo import i18n -from paste import deploy import pbr.version @@ -50,6 +49,7 @@ from keystone import config from keystone.i18n import _ from keystone.openstack.common import service from keystone.openstack.common import systemd +from keystone import service as keystone_service CONF = config.CONF @@ -73,7 +73,7 @@ class ServerWrapper(object): def create_server(conf, name, host, port, workers): - app = deploy.loadapp('config:%s' % conf, name=name) + app = keystone_service.loadapp('config:%s' % conf, name) server = environment.Server(app, host=host, port=port, keepalive=CONF.tcp_keepalive, keepidle=CONF.tcp_keepidle) diff --git a/httpd/keystone.py b/httpd/keystone.py index ca658959de..f5ce498c51 100644 --- a/httpd/keystone.py +++ b/httpd/keystone.py @@ -16,7 +16,6 @@ import logging import os from oslo import i18n -from paste import deploy # NOTE(dstanek): i18n.enable_lazy() must be called before @@ -32,6 +31,7 @@ from keystone.common import environment from keystone.common import sql from keystone import config from keystone.openstack.common import log +from keystone import service CONF = config.CONF @@ -55,7 +55,6 @@ drivers = backends.load_backends() # NOTE(ldbragst): 'application' is required in this context by WSGI spec. # The following is a reference to Python Paste Deploy documentation # http://pythonpaste.org/deploy/ -application = deploy.loadapp('config:%s' % config.find_paste_config(), - name=name) +application = service.loadapp('config:%s' % config.find_paste_config(), name) dependency.resolve_future_dependencies() diff --git a/keystone/common/json_home.py b/keystone/common/json_home.py index 3736854bc3..3a8c34c380 100644 --- a/keystone/common/json_home.py +++ b/keystone/common/json_home.py @@ -13,6 +13,9 @@ # under the License. +import six + + def build_v3_resource_relation(resource_name): return ('http://docs.openstack.org/api/openstack-identity/3/rel/%s' % resource_name) @@ -49,3 +52,13 @@ class Parameters(object): ROLE_ID = build_v3_parameter_relation('role_id') SERVICE_ID = build_v3_parameter_relation('service_id') USER_ID = build_v3_parameter_relation('user_id') + + +def translate_urls(json_home, new_prefix): + """Given a JSON Home document, sticks new_prefix on each of the urls.""" + + for dummy_rel, resource in six.iteritems(json_home['resources']): + if 'href' in resource: + resource['href'] = new_prefix + resource['href'] + elif 'href-template' in resource: + resource['href-template'] = new_prefix + resource['href-template'] diff --git a/keystone/controllers.py b/keystone/controllers.py index edabd283d6..52514af186 100644 --- a/keystone/controllers.py +++ b/keystone/controllers.py @@ -12,9 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. +import webob + from keystone.common import extension +from keystone.common import json_home from keystone.common import wsgi from keystone import exception +from keystone.openstack.common import jsonutils from keystone.openstack.common import log @@ -25,6 +29,31 @@ MEDIA_TYPE_XML = 'application/vnd.openstack.identity-%s+xml' _VERSIONS = [] +# NOTE(blk-u): latest_app will be set by keystone.service.loadapp(). It gets +# set to the application that was just loaded. In the case of keystone-all, +# loadapp() gets called twice, once for the public app and once for the admin +# app. In the case of httpd/keystone, 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': {}} + + req = webob.Request.blank( + '/v3', headers={'Accept': 'application/json-home'}) + v3_json_home_str = req.get_response(latest_app).body + v3_json_home = jsonutils.loads(v3_json_home_str) + 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.""" @@ -144,6 +173,14 @@ class Version(wsgi.Application): return versions def get_versions(self, context): + + req_mime_type = v3_mime_type_best_match(context) + 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(context) return wsgi.render_response(status=(300, 'Multiple Choices'), body={ 'versions': { diff --git a/keystone/service.py b/keystone/service.py index 47c46d6603..3d69c89cd4 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -15,6 +15,7 @@ import functools import sys +from paste import deploy import routes from keystone import assignment @@ -36,6 +37,14 @@ CONF = config.CONF LOG = log.getLogger(__name__) +def loadapp(conf, 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) + return controllers.latest_app + + def fail_gracefully(f): """Logs exceptions and aborts.""" @functools.wraps(f) diff --git a/keystone/tests/core.py b/keystone/tests/core.py index e38ba09ee3..79c1efe8b5 100644 --- a/keystone/tests/core.py +++ b/keystone/tests/core.py @@ -29,7 +29,6 @@ import fixtures from oslo.config import fixture as config_fixture import oslotest.base as oslotest from oslotest import mockpatch -from paste import deploy import six from testtools import testcase import webob @@ -53,6 +52,7 @@ from keystone import exception from keystone.i18n import _ from keystone import notifications from keystone.openstack.common import log +from keystone import service from keystone.tests import ksfixtures # NOTE(dstanek): Tests inheriting from TestCase depend on having the @@ -603,7 +603,7 @@ class TestCase(BaseTestCase): return config def loadapp(self, config, name='main'): - return deploy.loadapp(self._paste_config(config), name=name) + return service.loadapp(self._paste_config(config), name=name) def client(self, app, *args, **kw): return TestClient(app, *args, **kw) diff --git a/keystone/tests/test_versions.py b/keystone/tests/test_versions.py index a0bd335936..6954da33b2 100644 --- a/keystone/tests/test_versions.py +++ b/keystone/tests/test_versions.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import functools import random @@ -556,22 +557,35 @@ class VersionTestCase(tests.TestCase): data = jsonutils.loads(resp.body) self.assertEqual(data, v2_only_response) - def test_json_home_v3(self): - # If the request is /v3 and the Accept header is application/json-home - # then the server responds with a JSON Home document. - + def _test_json_home(self, path, exp_json_home_data): client = self.client(self.public_app) - resp = client.get('/v3', headers={'Accept': 'application/json-home'}) + resp = client.get(path, headers={'Accept': 'application/json-home'}) self.assertThat(resp.status, tt_matchers.Equals('200 OK')) self.assertThat(resp.headers['Content-Type'], tt_matchers.Equals('application/json-home')) + self.assertThat(jsonutils.loads(resp.body), + tt_matchers.Equals(exp_json_home_data)) + + def test_json_home_v3(self): + # If the request is /v3 and the Accept header is application/json-home + # then the server responds with a JSON Home document. + exp_json_home_data = { 'resources': V3_JSON_HOME_RESOURCES_INHERIT_DISABLED} - self.assertThat(jsonutils.loads(resp.body), - tt_matchers.Equals(exp_json_home_data)) + self._test_json_home('/v3', exp_json_home_data) + + def test_json_home_root(self): + # If the request is / and the Accept header is application/json-home + # then the server responds with a JSON Home document. + + exp_json_home_data = copy.deepcopy({ + 'resources': V3_JSON_HOME_RESOURCES_INHERIT_DISABLED}) + json_home.translate_urls(exp_json_home_data, '/v3') + + self._test_json_home('/', exp_json_home_data) def test_accept_type_handling(self): # Accept headers with multiple types and qvalues are handled. diff --git a/keystone/tests/unit/common/test_json_home.py b/keystone/tests/unit/common/test_json_home.py index 553b40fbce..48c886ddd7 100644 --- a/keystone/tests/unit/common/test_json_home.py +++ b/keystone/tests/unit/common/test_json_home.py @@ -13,6 +13,8 @@ # under the License. +import copy + from testtools import matchers from keystone.common import json_home @@ -57,3 +59,33 @@ class JsonHomeTest(tests.BaseTestCase): 'http://docs.openstack.org/api/openstack-identity/3/ext/%s/%s/' 'param/%s' % (extension_name, extension_version, parameter_name)) self.assertThat(relation, matchers.Equals(exp_relation)) + + def test_translate_urls(self): + href_rel = self.getUniqueString() + href = self.getUniqueString() + href_template_rel = self.getUniqueString() + href_template = self.getUniqueString() + href_vars = {self.getUniqueString(): self.getUniqueString()} + original_json_home = { + 'resources': { + href_rel: {'href': href}, + href_template_rel: { + 'href-template': href_template, + 'href-vars': href_vars} + } + } + + new_json_home = copy.deepcopy(original_json_home) + new_prefix = self.getUniqueString() + json_home.translate_urls(new_json_home, new_prefix) + + exp_json_home = { + 'resources': { + href_rel: {'href': new_prefix + href}, + href_template_rel: { + 'href-template': new_prefix + href_template, + 'href-vars': href_vars} + } + } + + self.assertThat(new_json_home, matchers.Equals(exp_json_home))