Add V3 JSON Home support to GET /
The server wasn't returning a JSON Home response for GET / when the Accept header is `application/json-home`. By returning the V3 JSON Home response for GET / a V3 client that supports JSON Home can GET either /v3 or / and use the response. The identity API should be able to be set to /. Closes-Bug: #1366589 Change-Id: I3191a85acf9d2f582f6b48a164cf5ac2bf84a8cf
This commit is contained in:
parent
12655bf172
commit
0b67673034
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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']
|
||||
|
@ -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': {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user