summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZiad Sawalha <github@highbridgellc.com>2011-12-21 22:46:32 -0600
committerZiad Sawalha <github@highbridgellc.com>2011-12-22 13:21:42 -0600
commit8a842139ddc78c7ccb7e93eb8749cba38359a078 (patch)
tree304d26fff61d556ad8ccf3e17ea21f4a4d7a5a4a
parent462af873c4e33a8209f9ee10c88f4a722b7bb8f7 (diff)
downloadkeystone-8a842139ddc78c7ccb7e93eb8749cba38359a078.tar.gz
keystone-8a842139ddc78c7ccb7e93eb8749cba38359a078.tar.xz
keystone-8a842139ddc78c7ccb7e93eb8749cba38359a078.zip
Fixed version response (bug 891555 and bug 843052)
891555: Version response was supposed to be wrapped in a 'versions' element, instead there was only one version being returned at the root. Fixed and added tests to validate that. 843052: requests for version ATOM were ignoring the atom+xml as a valid content-type. Fix required adding a new atom template file and adding a new mapping for the content-type/extension in the URL normalizer. Fixed and added tests to validate that. Change-Id: I259c060e592e11cfc06f59ac5a540178c7f2def2
-rw-r--r--keystone/content/admin/version.atom.tpl29
-rw-r--r--keystone/content/admin/version.json.tpl66
-rw-r--r--keystone/content/admin/version.xml.tpl44
-rw-r--r--keystone/content/service/version.atom.tpl29
-rw-r--r--keystone/content/service/version.json.tpl66
-rw-r--r--keystone/content/service/version.xml.tpl44
-rw-r--r--keystone/controllers/version.py4
-rw-r--r--keystone/middleware/url.py6
-rw-r--r--keystone/test/unit/test_controller_version.py139
-rw-r--r--keystone/test/unit/test_normalizingfilter.py6
-rwxr-xr-xkeystone/utils.py5
11 files changed, 322 insertions, 116 deletions
diff --git a/keystone/content/admin/version.atom.tpl b/keystone/content/admin/version.atom.tpl
new file mode 100644
index 00000000..e39250d5
--- /dev/null
+++ b/keystone/content/admin/version.atom.tpl
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <title type="text">Available API Versions</title>
+ <updated>2010-12-22T00:00:00.00Z</updated>
+ <id>http://identity.api.openstack.org/</id>
+ <author><name>OpenStack</name><uri>http://www.openstack.org/</uri></author>
+ <link rel="self" href="http://identity.api.openstack.org/"/>
+ <entry>
+ <id>http://identity.api.openstack.org/v2.0/</id>
+ <title type="text">Version v2.0</title>
+ <updated>2011-09-30T00:00:00.00Z</updated>
+ <link rel="self" href="http://identity.api.openstack.org/v2.0/"/>
+ <content type="text">Version v2.0 BETA (2011-09-30T00:00:00.00Z)</content>
+ </entry>
+ <entry>
+ <id>http://identity.api.openstack.org/v1.1/</id>
+ <title type="text">Version v1.1</title>
+ <updated>2011-01-21T11:33:21-06:00</updated>
+ <link rel="self" href="http://identity.api.openstack.org/v1.1/"/>
+ <content type="text">Version v1.1 CURRENT (2011-01-21T11:33:21-06:00)</content>
+ </entry>
+ <entry>
+ <id>http://identity.api.openstack.org/v1.0/</id>
+ <title type="text">Version v1.0</title>
+ <updated>2009-10-09T11:30:00Z</updated>
+ <link rel="self" href="http://identity.api.openstack.org/v1.0/"/>
+ <content type="text">Version v1.0 DEPRECATED (2009-10-09T11:30:00Z)</content>
+ </entry>
+</feed>
diff --git a/keystone/content/admin/version.json.tpl b/keystone/content/admin/version.json.tpl
index c7ca3a42..112ba1b8 100644
--- a/keystone/content/admin/version.json.tpl
+++ b/keystone/content/admin/version.json.tpl
@@ -1,38 +1,32 @@
{
- "version" : {
- "id" : "v{{API_VERSION}}",
- "status" : "{{API_VERSION_STATUS}}",
- "updated" : "{{API_VERSION_DATE}}",
- "links": [
- {
- "rel" : "self",
- "href" : "http://{{HOST}}:{{PORT}}/v{{API_VERSION}}/"
- },
- {
- "rel" : "describedby",
- "type" : "text/html",
- "href" : "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/"
- },
- {
- "rel" : "describedby",
- "type" : "application/pdf",
- "href" : "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf"
- },
- {
- "rel" : "describedby",
- "type" : "application/vnd.sun.wadl+xml",
- "href" : "http://{{HOST}}:{{PORT}}/v2.0/identity-admin.wadl"
- }
- ],
- "media-types": [
- {
- "base" : "application/xml",
- "type" : "application/vnd.openstack.identity-v{{API_VERSION}}+xml"
- },
- {
- "base" : "application/json",
- "type" : "application/vnd.openstack.identity-v{{API_VERSION}}+json"
- }
- ]
+ "versions": {
+ "values": [{
+ "id": "v{{API_VERSION}}",
+ "status": "{{API_VERSION_STATUS}}",
+ "updated": "{{API_VERSION_DATE}}",
+ "links": [{
+ "rel": "self",
+ "href": "http://{{HOST}}:{{PORT}}/v{{API_VERSION}}/"
+ }, {
+ "rel": "describedby",
+ "type": "text/html",
+ "href": "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/"
+ }, {
+ "rel": "describedby",
+ "type": "application/pdf",
+ "href": "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf"
+ }, {
+ "rel": "describedby",
+ "type": "application/vnd.sun.wadl+xml",
+ "href": "http://{{HOST}}:{{PORT}}/v2.0/identity-admin.wadl"
+ }],
+ "media-types": [{
+ "base": "application/xml",
+ "type": "application/vnd.openstack.identity-v{{API_VERSION}}+xml"
+ }, {
+ "base": "application/json",
+ "type": "application/vnd.openstack.identity-v{{API_VERSION}}+json"
+ }]
+ }]
}
-}
+} \ No newline at end of file
diff --git a/keystone/content/admin/version.xml.tpl b/keystone/content/admin/version.xml.tpl
index 5074d146..b62c9b6a 100644
--- a/keystone/content/admin/version.xml.tpl
+++ b/keystone/content/admin/version.xml.tpl
@@ -1,27 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
-<version xmlns="http://docs.openstack.org/common/api/v2.0"
- xmlns:atom="http://www.w3.org/2005/Atom"
- id="v{{API_VERSION}}" status="{{API_VERSION_STATUS}}" updated="{{API_VERSION_DATE}}">
+<versions xmlns="http://docs.openstack.org/common/api/v2.0"
+ xmlns:atom="http://www.w3.org/2005/Atom">
- <media-types>
- <media-type base="application/xml"
- type="application/vnd.openstack.identity-v{{API_VERSION}}+xml"/>
- <media-type base="application/json"
- type="application/vnd.openstack.identity-v{{API_VERSION}}+json"/>
- </media-types>
+ <version id="v{{API_VERSION}}" status="{{API_VERSION_STATUS}}" updated="{{API_VERSION_DATE}}">
- <atom:link rel="self"
- href="http://{{HOST}}:{{PORT}}/v{{API_VERSION}}/"/>
+ <media-types>
+ <media-type base="application/xml"
+ type="application/vnd.openstack.identity-v{{API_VERSION}}+xml"/>
+ <media-type base="application/json"
+ type="application/vnd.openstack.identity-v{{API_VERSION}}+json"/>
+ </media-types>
- <atom:link rel="describedby"
- type="text/html"
- href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/" />
+ <atom:link rel="self"
+ href="http://{{HOST}}:{{PORT}}/v{{API_VERSION}}/"/>
- <atom:link rel="describedby"
- type="application/pdf"
- href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf" />
+ <atom:link rel="describedby"
+ type="text/html"
+ href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/" />
- <atom:link rel="describedby"
- type="application/vnd.sun.wadl+xml"
- href="http://{{HOST}}:{{PORT}}/v2.0/identity-admin.wadl" />
-</version>
+ <atom:link rel="describedby"
+ type="application/pdf"
+ href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf" />
+
+ <atom:link rel="describedby"
+ type="application/vnd.sun.wadl+xml"
+ href="http://{{HOST}}:{{PORT}}/v2.0/identity-admin.wadl" />
+ </version>
+</versions> \ No newline at end of file
diff --git a/keystone/content/service/version.atom.tpl b/keystone/content/service/version.atom.tpl
new file mode 100644
index 00000000..e39250d5
--- /dev/null
+++ b/keystone/content/service/version.atom.tpl
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <title type="text">Available API Versions</title>
+ <updated>2010-12-22T00:00:00.00Z</updated>
+ <id>http://identity.api.openstack.org/</id>
+ <author><name>OpenStack</name><uri>http://www.openstack.org/</uri></author>
+ <link rel="self" href="http://identity.api.openstack.org/"/>
+ <entry>
+ <id>http://identity.api.openstack.org/v2.0/</id>
+ <title type="text">Version v2.0</title>
+ <updated>2011-09-30T00:00:00.00Z</updated>
+ <link rel="self" href="http://identity.api.openstack.org/v2.0/"/>
+ <content type="text">Version v2.0 BETA (2011-09-30T00:00:00.00Z)</content>
+ </entry>
+ <entry>
+ <id>http://identity.api.openstack.org/v1.1/</id>
+ <title type="text">Version v1.1</title>
+ <updated>2011-01-21T11:33:21-06:00</updated>
+ <link rel="self" href="http://identity.api.openstack.org/v1.1/"/>
+ <content type="text">Version v1.1 CURRENT (2011-01-21T11:33:21-06:00)</content>
+ </entry>
+ <entry>
+ <id>http://identity.api.openstack.org/v1.0/</id>
+ <title type="text">Version v1.0</title>
+ <updated>2009-10-09T11:30:00Z</updated>
+ <link rel="self" href="http://identity.api.openstack.org/v1.0/"/>
+ <content type="text">Version v1.0 DEPRECATED (2009-10-09T11:30:00Z)</content>
+ </entry>
+</feed>
diff --git a/keystone/content/service/version.json.tpl b/keystone/content/service/version.json.tpl
index 58d1fa4d..0eb824bf 100644
--- a/keystone/content/service/version.json.tpl
+++ b/keystone/content/service/version.json.tpl
@@ -1,38 +1,32 @@
{
- "version" : {
- "id" : "v{{API_VERSION}}",
- "status" : "{{API_VERSION_STATUS}}",
- "updated" : "{{API_VERSION_DATE}}",
- "links": [
- {
- "rel" : "self",
- "href" : "http://{{HOST}}:{{PORT}}/v2.0/"
- },
- {
- "rel" : "describedby",
- "type" : "text/html",
- "href" : "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/"
- },
- {
- "rel" : "describedby",
- "type" : "application/pdf",
- "href" : "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf"
- },
- {
- "rel" : "describedby",
- "type" : "application/vnd.sun.wadl+xml",
- "href" : "http://{{HOST}}:{{PORT}}/v2.0/identity.wadl"
- }
- ],
- "media-types": [
- {
- "base" : "application/xml",
- "type" : "application/vnd.openstack.identity-v2.0+xml"
- },
- {
- "base" : "application/json",
- "type" : "application/vnd.openstack.identity-v2.0+json"
- }
- ]
+ "versions": {
+ "values": [{
+ "id": "v{{API_VERSION}}",
+ "status": "{{API_VERSION_STATUS}}",
+ "updated": "{{API_VERSION_DATE}}",
+ "links": [{
+ "rel": "self",
+ "href": "http://{{HOST}}:{{PORT}}/v2.0/"
+ }, {
+ "rel": "describedby",
+ "type": "text/html",
+ "href": "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/"
+ }, {
+ "rel": "describedby",
+ "type": "application/pdf",
+ "href": "http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf"
+ }, {
+ "rel": "describedby",
+ "type": "application/vnd.sun.wadl+xml",
+ "href": "http://{{HOST}}:{{PORT}}/v2.0/identity.wadl"
+ }],
+ "media-types": [{
+ "base": "application/xml",
+ "type": "application/vnd.openstack.identity-v2.0+xml"
+ }, {
+ "base": "application/json",
+ "type": "application/vnd.openstack.identity-v2.0+json"
+ }]
+ }]
}
-}
+} \ No newline at end of file
diff --git a/keystone/content/service/version.xml.tpl b/keystone/content/service/version.xml.tpl
index 1d0dcc19..30a46489 100644
--- a/keystone/content/service/version.xml.tpl
+++ b/keystone/content/service/version.xml.tpl
@@ -1,27 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
-<version xmlns="http://docs.openstack.org/common/api/v2.0"
- xmlns:atom="http://www.w3.org/2005/Atom"
- id="v{{API_VERSION}}" status="{{API_VERSION_STATUS}}" updated="{{API_VERSION_DATE}}">
+<versions xmlns="http://docs.openstack.org/common/api/v2.0"
+ xmlns:atom="http://www.w3.org/2005/Atom">
- <media-types>
- <media-type base="application/xml"
- type="application/vnd.openstack.identity-v{{API_VERSION}}+xml"/>
- <media-type base="application/json"
- type="application/vnd.openstack.identity-v{{API_VERSION}}+json"/>
- </media-types>
+ <version id="v{{API_VERSION}}" status="{{API_VERSION_STATUS}}" updated="{{API_VERSION_DATE}}">
- <atom:link rel="self"
- href="http://{{HOST}}:{{PORT}}/v{{API_VERSION}}/"/>
+ <media-types>
+ <media-type base="application/xml"
+ type="application/vnd.openstack.identity-v{{API_VERSION}}+xml"/>
+ <media-type base="application/json"
+ type="application/vnd.openstack.identity-v{{API_VERSION}}+json"/>
+ </media-types>
- <atom:link rel="describedby"
- type="text/html"
- href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/" />
+ <atom:link rel="self"
+ href="http://{{HOST}}:{{PORT}}/v{{API_VERSION}}/"/>
- <atom:link rel="describedby"
- type="application/pdf"
- href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf" />
+ <atom:link rel="describedby"
+ type="text/html"
+ href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/content/" />
- <atom:link rel="describedby"
- type="application/vnd.sun.wadl+xml"
- href="http://{{HOST}}:{{PORT}}/v2.0/identity.wadl" />
-</version>
+ <atom:link rel="describedby"
+ type="application/pdf"
+ href="http://docs.openstack.org/api/openstack-identity-service/{{API_VERSION}}/identity-dev-guide-{{API_VERSION}}.pdf" />
+
+ <atom:link rel="describedby"
+ type="application/vnd.sun.wadl+xml"
+ href="http://{{HOST}}:{{PORT}}/v2.0/identity.wadl" />
+ </version>
+</versions> \ No newline at end of file
diff --git a/keystone/controllers/version.py b/keystone/controllers/version.py
index 4a4f8645..30fe1c31 100644
--- a/keystone/controllers/version.py
+++ b/keystone/controllers/version.py
@@ -25,6 +25,10 @@ class VersionController(wsgi.Controller):
resp_file = os.path.join(possible_topdir,
"keystone/content/%s.xml.tpl" % file)
resp.content_type = "application/xml"
+ elif utils.is_atom_response(req):
+ resp_file = os.path.join(possible_topdir,
+ "keystone/content/%s.atom.tpl" % file)
+ resp.content_type = "application/atom+xml"
else:
resp_file = os.path.join(possible_topdir,
"keystone/content/%s.json.tpl" % file)
diff --git a/keystone/middleware/url.py b/keystone/middleware/url.py
index 4bc214e4..fa2f6183 100644
--- a/keystone/middleware/url.py
+++ b/keystone/middleware/url.py
@@ -38,7 +38,8 @@ PATH_PREFIXES = {
# Maps supported URL extensions to RESPONSE_ENCODING
PATH_SUFFIXES = {
'.json': 'json',
- '.xml': 'xml'}
+ '.xml': 'xml',
+ '.atom': 'atom+xml'}
# Maps supported Accept headers to RESPONSE_ENCODING and API_VERSION
ACCEPT_HEADERS = {
@@ -49,7 +50,8 @@ ACCEPT_HEADERS = {
'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)}
+ 'application/xml': ('xml', None),
+ 'application/atom+xml': ('atom+xml', None)}
DEFAULT_RESPONSE_ENCODING = 'json'
DEFAULT_API_VERSION = '2.0'
diff --git a/keystone/test/unit/test_controller_version.py b/keystone/test/unit/test_controller_version.py
new file mode 100644
index 00000000..c935185e
--- /dev/null
+++ b/keystone/test/unit/test_controller_version.py
@@ -0,0 +1,139 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import logging
+from lxml import etree
+import unittest2 as unittest
+from webob import Request
+
+from keystone.controllers.version import VersionController
+
+LOGGER = logging.getLogger(__name__)
+
+
+class TestVersionController(unittest.TestCase):
+ def setUp(self):
+ self.controller = VersionController({})
+
+ def _default_version(self, file=None):
+ """ Verify default response for versions is JSON """
+ if file is None:
+ file = 'admin/version'
+ req = Request.blank('/')
+ req.environ = {}
+ response = self.controller.get_version_info(req, file=file)
+ self.assertEqual(response.content_type, 'application/json')
+ data = json.loads(response.body)
+ self.assertIsNotNone(data)
+
+ def _json_version(self, file=None):
+ """ Verify JSON response for versions
+
+ Checks that JSON is returned when Accept is set to application/json.
+ Also checks that verions and version exist and that
+ values are as expected
+
+ """
+ if file is None:
+ file = 'admin/version'
+ req = Request.blank('/')
+ req.headers['Accept'] = 'application/json'
+ req.environ = {}
+ response = self.controller.get_version_info(req, file=file)
+ self.assertEqual(response.content_type, 'application/json')
+ data = json.loads(response.body)
+ self.assertIn("versions", data)
+ versions = data['versions']
+ self.assertIn("values", versions)
+ values = versions['values']
+ self.assertIsInstance(values, type([]))
+ for version in values:
+ for item in version:
+ self.assertIn(item, ["id", "status", "updated", "links",
+ "media-types"])
+
+ def _xml_version(self, file=None):
+ """ Verify XML response for versions
+
+ Checks that XML is returned when Accept is set to application/xml.
+ Also checks that verions and version tags exist and that
+ attributes are as expected
+
+ """
+ if file is None:
+ file = 'admin/version'
+ req = Request.blank('/')
+ req.headers['Accept'] = 'application/xml'
+ req.environ = {}
+ response = self.controller.get_version_info(req, file=file)
+ self.assertEqual(response.content_type, 'application/xml')
+ data = etree.fromstring(response.body)
+ self.assertEqual(data.tag,
+ '{http://docs.openstack.org/common/api/v2.0}versions')
+ for version in data:
+ self.assertEqual(version.tag,
+ '{http://docs.openstack.org/common/api/v2.0}version')
+ for attribute in version.attrib:
+ self.assertIn(attribute, ["id", "status", "updated", "links",
+ "media-types"])
+
+ def _atom_version(self, file=None):
+ """ Verify ATOM response for versions
+
+ Checks that ATOM XML is returned when Accept is set to
+ aapplication/atom+xml.
+ Also checks that verions and version tags exist and that
+ attributes are as expected
+
+ """
+ if file is None:
+ file = 'admin/version'
+ req = Request.blank('/')
+ req.headers['Accept'] = 'application/atom+xml'
+ req.environ = {}
+ response = self.controller.get_version_info(req, file=file)
+ self.assertEqual(response.content_type, 'application/atom+xml')
+ data = etree.fromstring(response.body)
+ self.assertEqual(data.tag,
+ '{http://www.w3.org/2005/Atom}feed')
+
+ def test_default_version_admin(self):
+ self._default_version(file='admin/version')
+
+ def test_default_version_service(self):
+ self._default_version(file='service/version')
+
+ def test_xml_version_admin(self):
+ self._xml_version(file='admin/version')
+
+ def test_xml_version_service(self):
+ self._xml_version(file='service/version')
+
+ def test_json_version_admin(self):
+ self._json_version(file='admin/version')
+
+ def test_json_version_service(self):
+ self._json_version(file='service/version')
+
+ def test_atom_version_admin(self):
+ self._atom_version(file='admin/version')
+
+ def test_atom_version_service(self):
+ self._atom_version(file='service/version')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/keystone/test/unit/test_normalizingfilter.py b/keystone/test/unit/test_normalizingfilter.py
index e83af762..1b608729 100644
--- a/keystone/test/unit/test_normalizingfilter.py
+++ b/keystone/test/unit/test_normalizingfilter.py
@@ -60,6 +60,12 @@ class NormalizingFilterTest(unittest.TestCase):
self.assertEqual('/someresource', env['PATH_INFO'])
self.assertEqual('application/xml', env['HTTP_ACCEPT'])
+ def test_atom_extension(self):
+ env = {'PATH_INFO': '/v2.0/someresource.atom'}
+ self.filter(env, _start_response)
+ self.assertEqual('/someresource', env['PATH_INFO'])
+ self.assertEqual('application/atom+xml', env['HTTP_ACCEPT'])
+
def test_json_extension(self):
env = {'PATH_INFO': '/v2.0/someresource.json'}
self.filter(env, _start_response)
diff --git a/keystone/utils.py b/keystone/utils.py
index 440070de..2660451f 100755
--- a/keystone/utils.py
+++ b/keystone/utils.py
@@ -33,6 +33,11 @@ def is_json_response(req):
return "Accept" in req.headers and "application/json" in req.accept
+def is_atom_response(req):
+ """Returns True when the request wants an ATOM response, False otherwise"""
+ return "Accept" in req.headers and "application/atom+xml" in req.accept
+
+
def get_app_root():
return os.path.abspath(os.path.dirname(__file__))