summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-08-03 15:12:05 +0000
committerGerrit Code Review <review@openstack.org>2011-08-03 15:12:05 +0000
commitb4491d96705aa6070802d2c5a798723de261a9dc (patch)
tree1b3a5212807ceeb4291a43802c3e0ac460c62685
parent8ff14cde887ac59adf3d682f0b4f81dae0d4dd76 (diff)
parentb3c4f16ca84e4d19caef1c40266310bf090c0927 (diff)
Merge "Added support for versioned openstack MIME types"
-rwxr-xr-xkeystone/backends/sqlalchemy/api/endpoint_template.py64
-rw-r--r--keystone/common/template.py68
-rw-r--r--keystone/frontends/legacy_token_auth.py5
-rw-r--r--keystone/middleware/url.py147
-rw-r--r--keystone/routers/admin.py70
-rw-r--r--keystone/routers/service.py14
-rw-r--r--keystone/test/functional/sample_test.py7
-rw-r--r--keystone/test/system/test_request_specs.py12
8 files changed, 246 insertions, 141 deletions
diff --git a/keystone/backends/sqlalchemy/api/endpoint_template.py b/keystone/backends/sqlalchemy/api/endpoint_template.py
index 52231fec..f74afbec 100755
--- a/keystone/backends/sqlalchemy/api/endpoint_template.py
+++ b/keystone/backends/sqlalchemy/api/endpoint_template.py
@@ -63,31 +63,31 @@ class EndpointTemplateAPI(BaseEndpointTemplateAPI):
return (None, None)
if marker is None:
marker = first.id
- next = session.query(models.EndpointTemplates).filter("id > :marker").params(\
+ next_page = session.query(models.EndpointTemplates).filter("id > :marker").params(\
marker='%s' % marker).order_by(\
models.EndpointTemplates.id).limit(limit).all()
- prev = session.query(models.EndpointTemplates).filter("id < :marker").params(\
+ prev_page = session.query(models.EndpointTemplates).filter("id < :marker").params(\
marker='%s' % marker).order_by(\
models.EndpointTemplates.id.desc()).limit(int(limit)).all()
- if len(next) == 0:
- next = last
+ if len(next_page) == 0:
+ next_page = last
else:
- for t in next:
- next = t
- if len(prev) == 0:
- prev = first
+ for t in next_page:
+ next_page = t
+ if len(prev_page) == 0:
+ prev_page = first
else:
- for t in prev:
- prev = t
- if prev.id == marker:
- prev = None
+ for t in prev_page:
+ prev_page = t
+ if prev_page.id == marker:
+ prev_page = None
else:
- prev = prev.id
- if next.id == last.id:
- next = None
+ prev_page = prev_page.id
+ if next_page.id == last.id:
+ next_page = None
else:
- next = next.id
- return (prev, next)
+ next_page = next_page.id
+ return (prev_page, next_page)
def endpoint_get_by_tenant_get_page(self, tenant_id, marker, limit,
session=None):
@@ -119,39 +119,39 @@ class EndpointTemplateAPI(BaseEndpointTemplateAPI):
return (None, None)
if marker is None:
marker = first.id
- next = session.query(tba).\
+ next_page = session.query(tba).\
filter(tba.tenant_id == tenant_id).\
filter("id>=:marker").params(
marker='%s' % marker).order_by(
tba.id).limit(int(limit)).all()
- prev = session.query(tba).\
+ prev_page = session.query(tba).\
filter(tba.tenant_id == tenant_id).\
filter("id < :marker").params(
marker='%s' % marker).order_by(
tba.id).limit(int(limit) + 1).all()
- next_len = len(next)
- prev_len = len(prev)
+ next_len = len(next_page)
+ prev_len = len(prev_page)
if next_len == 0:
- next = last
+ next_page = last
else:
- for t in next:
- next = t
+ for t in next_page:
+ next_page = t
if prev_len == 0:
- prev = first
+ prev_page = first
else:
- for t in prev:
- prev = t
+ for t in prev_page:
+ prev_page = t
if first.id == marker:
- prev = None
+ prev_page = None
else:
- prev = prev.id
+ prev_page = prev_page.id
if marker == last.id:
- next = None
+ next_page = None
else:
- next = next.id
- return (prev, next)
+ next_page = next_page.id
+ return (prev_page, next_page)
def endpoint_add(self, values):
endpoints = models.Endpoints()
diff --git a/keystone/common/template.py b/keystone/common/template.py
index 97e52658..308c8fd1 100644
--- a/keystone/common/template.py
+++ b/keystone/common/template.py
@@ -64,7 +64,7 @@ class BaseTemplate(object):
settings = {} #used in prepare()
defaults = {} #used in render()
- def __init__(self, source=None, name=None, lookup=[], encoding='utf8',
+ def __init__(self, source=None, name=None, lookup=None, encoding='utf8',
**settings):
""" Create a new template.
If the source parameter (str or buffer) is missing, the name argument
@@ -76,10 +76,12 @@ class BaseTemplate(object):
The encoding parameter should be used to decode byte strings or files.
The settings parameter contains a dict for engine-specific settings.
"""
+ lookup = lookup or []
+
self.name = name
self.source = source.read() if hasattr(source, 'read') else source
self.filename = source.filename if hasattr(source, 'filename') else None
- self.lookup = map(os.path.abspath, lookup)
+ self.lookup = [os.path.abspath(path) for path in lookup]
self.encoding = encoding
self.settings = self.settings.copy() # Copy from class variable
self.settings.update(settings) # Apply
@@ -92,9 +94,11 @@ class BaseTemplate(object):
self.prepare(**self.settings)
@classmethod
- def search(cls, name, lookup=[]):
+ def search(cls, name, lookup=None):
""" Search name in all directories specified in lookup.
First without, then with common extensions. Return first hit. """
+ lookup = lookup or []
+
if os.path.isfile(name):
return name
for spath in lookup:
@@ -129,17 +133,23 @@ class BaseTemplate(object):
class SimpleTemplate(BaseTemplate):
- blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while', 'with', 'def', 'class')
+ blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while',
+ 'with', 'def', 'class')
dedent_blocks = ('elif', 'else', 'except', 'finally')
-
+ cache = None
+ code = None
+ compiled = None
+ _str = None
+ _escape = None
+
def prepare(self, escape_func=cgi.escape, noescape=False):
self.cache = {}
if self.source:
self.code = self.translate(self.source)
- self.co = compile(self.code, '<string>', 'exec')
+ self.compiled = compile(self.code, '<string>', 'exec')
else:
self.code = self.translate(open(self.filename).read())
- self.co = compile(self.code, self.filename, 'exec')
+ self.compiled = compile(self.code, self.filename, 'exec')
enc = self.encoding
touni = functools.partial(unicode, encoding=self.encoding)
self._str = lambda x: touni(x, enc)
@@ -158,7 +168,8 @@ class SimpleTemplate(BaseTemplate):
def yield_tokens(line):
for i, part in enumerate(re.split(r'\{\{(.*?)\}\}', line)):
if i % 2:
- if part.startswith('!'): yield 'RAW', part[1:]
+ if part.startswith('!'):
+ yield 'RAW', part[1:]
else: yield 'CMD', part
else: yield 'TXT', part
@@ -172,11 +183,14 @@ class SimpleTemplate(BaseTemplate):
for token in tokens:
if token[0] == tokenize.COMMENT:
start, end = token[2][1], token[3][1]
- return codeline[:start] + codeline[end:], codeline[start:end]
+ return (
+ codeline[:start] + codeline[end:],
+ codeline[start:end])
return line, ''
def flush(): # Flush the ptrbuffer
- if not ptrbuffer: return
+ if not ptrbuffer:
+ return
cline = ''
for line in ptrbuffer:
for token, value in line:
@@ -205,10 +219,12 @@ class SimpleTemplate(BaseTemplate):
else unicode(line, encoding=self.encoding)
if lineno <= 2:
m = re.search(r"%.*coding[:=]\s*([-\w\.]+)", line)
- if m: self.encoding = m.group(1)
- if m: line = line.replace('coding', 'coding (removed)')
+ if m:
+ self.encoding = m.group(1)
+ if m:
+ line = line.replace('coding', 'coding (removed)')
if line.strip()[:2].count('%') == 1:
- line = line.split('%', 1)[1].lstrip() # Full line following the %
+ line = line.split('%', 1)[1].lstrip() # Rest of line after %
cline = split_comment(line)[0].strip()
cmd = re.split(r'[^a-zA-Z0-9_]', cline)[0]
flush() ##encodig (TODO: why?)
@@ -235,7 +251,8 @@ class SimpleTemplate(BaseTemplate):
elif cmd == 'rebase':
p = cline.split(None, 2)[1:]
if len(p) == 2:
- code("globals()['_rebase']=(%s, dict(%s))" % (repr(p[0]), p[1]))
+ code("globals()['_rebase']=(%s, dict(%s))" % (
+ repr(p[0]), p[1]))
elif p:
code("globals()['_rebase']=(%s, {})" % repr(p[0]))
else:
@@ -258,7 +275,7 @@ class SimpleTemplate(BaseTemplate):
'_include': self.subtemplate, '_str': self._str,
'_escape': self._escape})
env.update(args)
- eval(self.co, env)
+ eval(self.compiled, env)
if '_rebase' in env:
subtpl, rargs = env['_rebase']
subtpl = self.__class__(name=subtpl, lookup=self.lookup)
@@ -273,7 +290,8 @@ class SimpleTemplate(BaseTemplate):
self.execute(stdout, **args)
return ''.join(stdout)
-def static_file(resp, req, filename, root, guessmime=True, mimetype=None, download=False):
+def static_file(resp, req, filename, root, guessmime=True, mimetype=None,
+ download=False):
""" Opens a file in a safe way and returns a HTTPError object with status
code 200, 305, 401 or 404. Sets Content-Type, Content-Length and
Last-Modified header. Obeys If-Modified-Since header and HEAD requests.
@@ -281,13 +299,10 @@ def static_file(resp, req, filename, root, guessmime=True, mimetype=None, downlo
root = os.path.abspath(root) + os.sep
filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
if not filename.startswith(root):
- #return HTTPError(403, "Access denied.")
return ForbiddenFault("Access denied.")
if not os.path.exists(filename) or not os.path.isfile(filename):
- #return HTTPError(404, "File does not exist.")
return fault.ItemNotFoundFault("File does not exist.")
if not os.access(filename, os.R_OK):
- #return HTTPError(403, "You do not have permission to access this file.")
return ForbiddenFault("You do not have permission to access this file.")
if not mimetype and guessmime:
@@ -309,13 +324,15 @@ def static_file(resp, req, filename, root, guessmime=True, mimetype=None, downlo
ims = parse_date(ims)
if ims is not None and ims >= int(stats.st_mtime):
- resp.date = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
+ resp.date = time.strftime(
+ "%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
return Response(body=None, status=304, headerlist=resp.headerlist)
resp.content_length = stats.st_size
if req.method == 'HEAD':
return Response(body=None, status=200, headerlist=resp.headerlist)
else:
- return Response(body=open(filename).read(), status=200, headerlist=resp.headerlist)
+ return Response(body=open(filename).read(), status=200,
+ headerlist=resp.headerlist)
def template(tpl, template_adapter=SimpleTemplate, **kwargs):
@@ -328,11 +345,14 @@ def template(tpl, template_adapter=SimpleTemplate, **kwargs):
lookup = kwargs.get('template_lookup', TEMPLATE_PATH)
if isinstance(tpl, template_adapter):
TEMPLATES[tpl] = tpl
- if settings: TEMPLATES[tpl].prepare(**settings)
+ if settings:
+ TEMPLATES[tpl].prepare(**settings)
elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
- TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup, **settings)
+ TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup,
+ **settings)
else:
- TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup, **settings)
+ TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup,
+ **settings)
return TEMPLATES[tpl].render(**kwargs)
diff --git a/keystone/frontends/legacy_token_auth.py b/keystone/frontends/legacy_token_auth.py
index 1d7cfc64..ff1f67ce 100644
--- a/keystone/frontends/legacy_token_auth.py
+++ b/keystone/frontends/legacy_token_auth.py
@@ -65,13 +65,12 @@ class AuthProtocol(object):
self.start_response = start_response
self.env = env
self.request = Request(env)
- if self.request.path.startswith('/v1.0'
- ) or self.request.path.startswith('/v1.1'):
+ if env['KEYSTONE_API_VERSION'] in ['1.0', '1.1']:
params = {"passwordCredentials":
{"username": utils.get_auth_user(self.request),
"password": utils.get_auth_key(self.request)}}
#Make request to keystone
- new_request = Request.blank('/v2.0/tokens')
+ new_request = Request.blank('/tokens')
new_request.method = 'POST'
new_request.headers['Content-type'] = 'application/json'
new_request.accept = 'text/json'
diff --git a/keystone/middleware/url.py b/keystone/middleware/url.py
index fdbcf794..d0643e77 100644
--- a/keystone/middleware/url.py
+++ b/keystone/middleware/url.py
@@ -27,52 +27,127 @@ overwrites the Accept header in the request, if present.
"""
-CONTENT_TYPES = {'json': 'application/json', 'xml': 'application/xml'}
-DEFAULT_CONTENT_TYPE = CONTENT_TYPES['json']
+import webob.acceptparse
-class UrlRewriteFilter(object):
- """Middleware filter to handle URL rewriting"""
+# Maps supported URL prefixes to API_VERSION
+PATH_PREFIXES = {
+ '/v2.0': '2.0',
+ '/v1.1': '1.1',
+ '/v1.0': '1.0'}
+
+# Maps supported URL extensions to RESPONSE_ENCODING
+PATH_SUFFIXES = {
+ '.json': 'json',
+ '.xml': 'xml'}
+
+# Maps supported Accept headers to RESPONSE_ENCODING and API_VERSION
+ACCEPT_HEADERS = {
+ 'application/vnd.openstack.identity-v2.0+json': ('json', '2.0'),
+ 'application/vnd.openstack.identity-v2.0+xml': ('xml', '2.0'),
+ 'application/vnd.openstack.identity-v1.1+json': ('json', '1.1'),
+ 'application/vnd.openstack.identity-v1.1+xml': ('xml', '1.1'),
+ 'application/vnd.openstack.identity-v1.0+json': ('json', '1.0'),
+ 'application/vnd.openstack.identity-v1.0+xml': ('xml', '1.0'),
+ 'application/json': ('json', None),
+ 'application/xml': ('xml', None)}
+
+DEFAULT_RESPONSE_ENCODING = 'json'
+DEFAULT_API_VERSION = '2.0'
+
+class NormalizingFilter(object):
+ """Middleware filter to handle URL and Accept header normalization"""
def __init__(self, app, conf):
# app is the next app in WSGI chain - eventually the OpenStack service
self.app = app
self.conf = conf
-
+
def __call__(self, env, start_response):
- (env['PATH_INFO'], env['HTTP_ACCEPT']) = self.override_accept_header(
- env.get('PATH_INFO'), env.get('HTTP_ACCEPT'))
-
- env['PATH_INFO'] = self.remove_trailing_slash(env.get('PATH_INFO'))
+ # Inspect the request for mime type and API version
+ env = normalize_accept_header(env)
+ env = normalize_path_prefix(env)
+ env = normalize_path_suffix(env)
+ env['PATH_INFO'] = normalize_starting_slash(env.get('PATH_INFO'))
+ env['PATH_INFO'] = normalize_trailing_slash(env['PATH_INFO'])
+ # Fall back on defaults, if necessary
+ env['KEYSTONE_API_VERSION'] = env.get(
+ 'KEYSTONE_API_VERSION') or DEFAULT_API_VERSION
+ env['KEYSTONE_RESPONSE_ENCODING'] = env.get(
+ 'KEYSTONE_RESPONSE_ENCODING') or DEFAULT_RESPONSE_ENCODING
+ env['HTTP_ACCEPT'] = 'application/' + (env.get(
+ 'KEYSTONE_RESPONSE_ENCODING') or DEFAULT_RESPONSE_ENCODING)
+
return self.app(env, start_response)
- def override_accept_header(self, path_info, http_accept):
- """Looks for an (.json/.xml) extension on the URL, removes it, and
- overrides the Accept header if an extension was found"""
- # try to split the extension from the rest of the path
- parts = path_info.rsplit('.', 1)
- if len(parts) > 1:
- (path, ext) = parts
- else:
- (path, ext) = (parts[0], None)
-
- if ext in CONTENT_TYPES:
- # Use the content type specified by the extension
- return (path, CONTENT_TYPES[ext])
- elif http_accept is None or http_accept == '*/*':
- # TODO: This probably isn't the best place to handle "Accept: */*"
- # No extension or Accept header specified, use default
- return (path_info, DEFAULT_CONTENT_TYPE)
- else:
- # Return what we were given
- return (path_info, http_accept)
+def normalize_accept_header(env):
+ """Matches the preferred Accept encoding to supported encodings.
+
+ Sets KEYSTONE_RESPONSE_ENCODING and KEYSTONE_API_VERSION, if appropriate."""
+ if env.get('HTTP_ACCEPT'):
+ accept = webob.acceptparse.Accept('Accept', env.get('HTTP_ACCEPT'))
+ best_accept = accept.best_match(ACCEPT_HEADERS.keys())
+ if best_accept:
+ response_encoding, api_version = ACCEPT_HEADERS[best_accept]
+
+ if response_encoding:
+ env['KEYSTONE_RESPONSE_ENCODING'] = response_encoding
+
+ if api_version:
+ env['KEYSTONE_API_VERSION'] = api_version
+
+ return env
+
+def normalize_path_prefix(env):
+ """Handles recognized PATH_INFO prefixes.
+
+ Looks for a version prefix on the PATH_INFO, sets KEYSTONE_API_VERSION
+ accordingly, and removes the prefix to normalize the request."""
+ for prefix in PATH_PREFIXES.keys():
+ if env['PATH_INFO'].startswith(prefix):
+ env['KEYSTONE_API_VERSION'] = PATH_PREFIXES[prefix]
+ env['PATH_INFO'] = env['PATH_INFO'][len(prefix):]
+ break
+
+ return env
+
+def normalize_path_suffix(env):
+ """Hnadles recognized PATH_INFO suffixes.
+
+ Looks for a recognized suffix on the PATH_INFO, sets the
+ KEYSTONE_RESPONSE_ENCODING accordingly, and removes the suffix to normalize
+ the request."""
+ for suffix in PATH_SUFFIXES.keys():
+ if env['PATH_INFO'].endswith(suffix):
+ env['KEYSTONE_RESPONSE_ENCODING'] = PATH_SUFFIXES[suffix]
+ env['PATH_INFO'] = env['PATH_INFO'][:-len(suffix)]
+ break
+
+ return env
+
+def normalize_starting_slash(path_info):
+ """Removes a trailing slash from the given path, if any."""
+ # Ensure the path at least contains a slash
+ if not path_info:
+ return '/'
+
+ # Ensure the path starts with a slash
+ elif path_info[0] != '/':
+ return '/' + path_info
+
+ # No need to change anything
+ else:
+ return path_info
+
+def normalize_trailing_slash(path_info):
+ """Removes a trailing slash from the given path, if any."""
+ # Remove trailing slash, unless it's the only char
+ if len(path_info) > 1 and path_info[-1] == '/':
+ return path_info[:-1]
- def remove_trailing_slash(self, path_info):
- """Removes a trailing slash from the given path, if any"""
- if len(path_info) > 1 and path_info[-1] == '/':
- return path_info[:-1]
- else:
- return path_info
+ # No need to change anything
+ else:
+ return path_info
def filter_factory(global_conf, **local_conf):
"""Returns a WSGI filter app for use with paste.deploy."""
@@ -80,5 +155,5 @@ def filter_factory(global_conf, **local_conf):
conf.update(local_conf)
def ext_filter(app):
- return UrlRewriteFilter(app, conf)
+ return NormalizingFilter(app, conf)
return ext_filter
diff --git a/keystone/routers/admin.py b/keystone/routers/admin.py
index d23baeba..62c17ea5 100644
--- a/keystone/routers/admin.py
+++ b/keystone/routers/admin.py
@@ -19,136 +19,136 @@ class AdminApi(wsgi.Router):
mapper = routes.Mapper()
db.configure_backends(options)
-
+
# Token Operations
auth_controller = AuthController(options)
- mapper.connect("/v2.0/tokens", controller=auth_controller,
+ mapper.connect("/tokens", controller=auth_controller,
action="authenticate",
conditions=dict(method=["POST"]))
- mapper.connect("/v2.0/tokens/{token_id}", controller=auth_controller,
+ mapper.connect("/tokens/{token_id}", controller=auth_controller,
action="validate_token",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/tokens/{token_id}", controller=auth_controller,
+ mapper.connect("/tokens/{token_id}", controller=auth_controller,
action="delete_token",
conditions=dict(method=["DELETE"]))
# Tenant Operations
tenant_controller = TenantController(options)
- mapper.connect("/v2.0/tenants", controller=tenant_controller,
+ mapper.connect("/tenants", controller=tenant_controller,
action="create_tenant",
conditions=dict(method=["PUT", "POST"]))
- mapper.connect("/v2.0/tenants", controller=tenant_controller,
+ mapper.connect("/tenants", controller=tenant_controller,
action="get_tenants", conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/tenants/{tenant_id}",
+ mapper.connect("/tenants/{tenant_id}",
controller=tenant_controller,
action="get_tenant", conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/tenants/{tenant_id}",
+ mapper.connect("/tenants/{tenant_id}",
controller=tenant_controller,
action="update_tenant", conditions=dict(method=["PUT"]))
- mapper.connect("/v2.0/tenants/{tenant_id}",
+ mapper.connect("/tenants/{tenant_id}",
controller=tenant_controller,
action="delete_tenant", conditions=dict(method=["DELETE"]))
- # User Operations
+ # User Operations
user_controller = UserController(options)
- mapper.connect("/v2.0/users",
+ mapper.connect("/users",
controller=user_controller,
action="create_user",
conditions=dict(method=["PUT", "POST"]))
- mapper.connect("/v2.0/users",
+ mapper.connect("/users",
controller=user_controller,
action="get_users",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/users/{user_id}",
+ mapper.connect("/users/{user_id}",
controller=user_controller,
action="get_user",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/users/{user_id}",
+ mapper.connect("/users/{user_id}",
controller=user_controller,
action="update_user",
conditions=dict(method=["PUT"]))
- mapper.connect("/v2.0/users/{user_id}",
+ mapper.connect("/users/{user_id}",
controller=user_controller,
action="delete_user",
conditions=dict(method=["DELETE"]))
- mapper.connect("/v2.0/users/{user_id}/password",
+ mapper.connect("/users/{user_id}/password",
controller=user_controller,
action="set_user_password",
conditions=dict(method=["PUT"]))
- mapper.connect("/v2.0/users/{user_id}/tenant",
+ mapper.connect("/users/{user_id}/tenant",
controller=user_controller,
action="update_user_tenant",
conditions=dict(method=["PUT"]))
- # Test this, test failed
- mapper.connect("/v2.0/users/{user_id}/enabled",
+ # Test this, test failed
+ mapper.connect("/users/{user_id}/enabled",
controller=user_controller,
action="set_user_enabled",
conditions=dict(method=["PUT"]))
- mapper.connect("/v2.0/tenants/{tenant_id}/users",
+ mapper.connect("/tenants/{tenant_id}/users",
controller=user_controller,
action="get_tenant_users",
conditions=dict(method=["GET"]))
#Roles and RoleRefs
roles_controller = RolesController(options)
- mapper.connect("/v2.0/roles", controller=roles_controller,
+ mapper.connect("/roles", controller=roles_controller,
action="get_roles", conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/roles/{role_id}", controller=roles_controller,
+ mapper.connect("/roles/{role_id}", controller=roles_controller,
action="get_role", conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/users/{user_id}/roleRefs",
+ mapper.connect("/users/{user_id}/roleRefs",
controller=roles_controller, action="get_role_refs",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/users/{user_id}/roleRefs",
+ mapper.connect("/users/{user_id}/roleRefs",
controller=roles_controller, action="create_role_ref",
conditions=dict(method=["POST"]))
- mapper.connect("/v2.0/users/{user_id}/roleRefs/{role_ref_id}",
+ mapper.connect("/users/{user_id}/roleRefs/{role_ref_id}",
controller=roles_controller, action="delete_role_ref",
conditions=dict(method=["DELETE"]))
#EndpointTemplatesControllers and Endpoints
endpoint_templates_controller = EndpointTemplatesController(options)
- mapper.connect("/v2.0/endpointTemplates",
+ mapper.connect("/endpointTemplates",
controller=endpoint_templates_controller,
action="get_endpoint_templates",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/endpointTemplates/{endpoint_templates_id}",
+ mapper.connect("/endpointTemplates/{endpoint_templates_id}",
controller=endpoint_templates_controller,
action="get_endpoint_template",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/tenants/{tenant_id}/endpoints",
+ mapper.connect("/tenants/{tenant_id}/endpoints",
controller=endpoint_templates_controller,
action="get_endpoints_for_tenant",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/tenants/{tenant_id}/endpoints",
+ mapper.connect("/tenants/{tenant_id}/endpoints",
controller=endpoint_templates_controller,
action="add_endpoint_to_tenant",
conditions=dict(method=["POST"]))
mapper.connect(
- "/v2.0/tenants/{tenant_id}/endpoints/{endpoints_id}",
+ "/tenants/{tenant_id}/endpoints/{endpoints_id}",
controller=endpoint_templates_controller,
action="remove_endpoint_from_tenant",
conditions=dict(method=["DELETE"]))
# Miscellaneous Operations
version_controller = VersionController(options)
- mapper.connect("/v2.0", controller=version_controller,
+ mapper.connect("/", controller=version_controller,
action="get_version_info",
conditions=dict(method=["GET"]))
# Static Files Controller
static_files_controller = StaticFilesController(options)
- mapper.connect("/v2.0/identitydevguide.pdf",
+ mapper.connect("/identitydevguide.pdf",
controller=static_files_controller,
action="get_pdf_contract",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/identity.wadl",
+ mapper.connect("/identity.wadl",
controller=static_files_controller,
action="get_wadl_contract",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/xsd/{xsd}",
+ mapper.connect("/xsd/{xsd}",
controller=static_files_controller,
action="get_xsd_contract",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/xsd/atom/{xsd}",
+ mapper.connect("/xsd/atom/{xsd}",
controller=static_files_controller,
action="get_xsd_atom_contract",
conditions=dict(method=["GET"]))
diff --git a/keystone/routers/service.py b/keystone/routers/service.py
index 6039274d..1219cc32 100644
--- a/keystone/routers/service.py
+++ b/keystone/routers/service.py
@@ -18,36 +18,36 @@ class ServiceApi(wsgi.Router):
# Token Operations
auth_controller = AuthController(options)
- mapper.connect("/v2.0/tokens", controller=auth_controller,
+ mapper.connect("/tokens", controller=auth_controller,
action="authenticate",
conditions=dict(method=["POST"]))
# Tenant Operations
tenant_controller = TenantController(options)
- mapper.connect("/v2.0/tenants", controller=tenant_controller,
+ mapper.connect("/tenants", controller=tenant_controller,
action="get_tenants", conditions=dict(method=["GET"]))
# Miscellaneous Operations
version_controller = VersionController(options)
- mapper.connect("/v2.0", controller=version_controller,
+ mapper.connect("/", controller=version_controller,
action="get_version_info",
conditions=dict(method=["GET"]))
# Static Files Controller
static_files_controller = StaticFilesController(options)
- mapper.connect("/v2.0/identitydevguide.pdf",
+ mapper.connect("/identitydevguide.pdf",
controller=static_files_controller,
action="get_pdf_contract",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/identity.wadl",
+ mapper.connect("/identity.wadl",
controller=static_files_controller,
action="get_wadl_contract",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/xsd/{xsd}",
+ mapper.connect("/xsd/{xsd}",
controller=static_files_controller,
action="get_pdf_contract",
conditions=dict(method=["GET"]))
- mapper.connect("/v2.0/xsd/atom/{xsd}",
+ mapper.connect("/xsd/atom/{xsd}",
controller=static_files_controller,
action="get_pdf_contract",
conditions=dict(method=["GET"]))
diff --git a/keystone/test/functional/sample_test.py b/keystone/test/functional/sample_test.py
index 10d24abe..6143829e 100644
--- a/keystone/test/functional/sample_test.py
+++ b/keystone/test/functional/sample_test.py
@@ -37,7 +37,6 @@
## @dtest.istest decorator). Adhere to these rules, and DTest can
## discover and run the tests without you having to do anything other
## than create them.
-import dtest
from dtest import util
## The "base" module contains KeystoneTest, which ensures that there's
@@ -60,8 +59,8 @@ class SampleTest(base.KeystoneTest):
## You don't *have* to declare a doc string, but it's good
## practice.
- ## Here we're making a "sample_call()", passing self.token as
- ## the authentication token. For available calls and the
+ ## Here we're making a sample call to "validate_token()", passing
+ ## self.token as the authentication token. For available calls and the
## order of arguments, check out ksapi.py. The return value
## will be an httplib.HTTPResponse object with additional
## 'body' (str) and 'obj' (dict) attributes. If a status code
@@ -70,7 +69,7 @@ class SampleTest(base.KeystoneTest):
## attached to the 'response' attribute of the exception, and
## the status will be on the 'status' attribute of the
## exception. Note that redirects are followed.
- resp = self.ks.sample_call(self.token, 'argument 1', 'argument 2')
+ resp = self.ks.validate_token(self.token, 'argument 1', 'argument 2')
# Verify that resp is correct
util.assert_equal(resp.status, 200)
diff --git a/keystone/test/system/test_request_specs.py b/keystone/test/system/test_request_specs.py
index 97b1cf48..cc8a24b7 100644
--- a/keystone/test/system/test_request_specs.py
+++ b/keystone/test/system/test_request_specs.py
@@ -38,6 +38,18 @@ class TestContentTypes(KeystoneTestCase):
r = self.service_request(headers={'Accept': 'application/json'})
self.assertTrue('application/json' in r.getheader('Content-Type'))
+ def test_versioned_xml_accept_header(self):
+ """Service responds to versioned xml Accept header"""
+ r = self.service_request(headers={
+ 'Accept': 'application/vnd.openstack.identity-v2.0+xml'})
+ self.assertTrue('application/xml' in r.getheader('Content-Type'))
+
+ def test_versioned_json_accept_header(self):
+ """Service responds to versioned json Accept header"""
+ r = self.service_request(headers={
+ 'Accept': 'application/vnd.openstack.identity-v2.0+json'})
+ self.assertTrue('application/json' in r.getheader('Content-Type'))
+
def test_xml_extension_overrides_conflicting_header(self):
"""Service returns XML when Accept header conflicts with extension"""
r = self.service_request(path='.xml',