Change v3 version API to v2.1

This patch changes v3 version API to v2.1 and makes v2 unit tests
share between v2 and v2.1.
v2.1 API does not support XML format. Unit tests related to XML
don't work against v2.1.

Partially implements blueprint v2-on-v3-api

Change-Id: I7abadb0c9082b9e90a98938967f0e27d1a2fa766
This commit is contained in:
Ken'ichi Ohmichi 2014-08-30 04:14:04 +00:00
parent 9e3e52afd3
commit fe5f22de04
9 changed files with 137 additions and 205 deletions

View File

@ -12,10 +12,10 @@
"updated": "2011-01-21T11:33:21Z" "updated": "2011-01-21T11:33:21Z"
}, },
{ {
"id": "v3.0", "id": "v2.1",
"links": [ "links": [
{ {
"href": "http://openstack.example.com/v3/", "href": "http://openstack.example.com/v2/",
"rel": "self" "rel": "self"
} }
], ],

View File

@ -3,7 +3,7 @@
<version status="CURRENT" updated="2011-01-21T11:33:21Z" id="v2.0"> <version status="CURRENT" updated="2011-01-21T11:33:21Z" id="v2.0">
<atom:link href="http://openstack.example.com/v2/" rel="self"/> <atom:link href="http://openstack.example.com/v2/" rel="self"/>
</version> </version>
<version status="EXPERIMENTAL" updated="2013-07-23T11:33:21Z" id="v3.0"> <version status="EXPERIMENTAL" updated="2013-07-23T11:33:21Z" id="v2.1">
<atom:link href="http://openstack.example.com/v3/" rel="self"/> <atom:link href="http://openstack.example.com/v2/" rel="self"/>
</version> </version>
</versions> </versions>

View File

@ -25,7 +25,7 @@ ALIAS = "versions"
class VersionsController(object): class VersionsController(object):
@extensions.expected_errors(404) @extensions.expected_errors(404)
def show(self, req, id='v3.0'): def show(self, req, id='v2.1'):
builder = views_versions.get_view_builder(req) builder = views_versions.get_view_builder(req)
if id not in versions.VERSIONS: if id not in versions.VERSIONS:
raise webob.exc.HTTPNotFound() raise webob.exc.HTTPNotFound()

View File

@ -29,7 +29,7 @@ LINKS = {
'v2.0': { 'v2.0': {
'html': 'http://docs.openstack.org/' 'html': 'http://docs.openstack.org/'
}, },
'v3.0': { 'v2.1': {
'html': 'http://docs.openstack.org/' 'html': 'http://docs.openstack.org/'
}, },
} }
@ -58,21 +58,21 @@ VERSIONS = {
} }
], ],
}, },
"v3.0": { "v2.1": {
"id": "v3.0", "id": "v2.1",
"status": "EXPERIMENTAL", "status": "EXPERIMENTAL",
"updated": "2013-07-23T11:33:21Z", "updated": "2013-07-23T11:33:21Z",
"links": [ "links": [
{ {
"rel": "describedby", "rel": "describedby",
"type": "text/html", "type": "text/html",
"href": LINKS['v3.0']['html'], "href": LINKS['v2.1']['html'],
}, },
], ],
"media-types": [ "media-types": [
{ {
"base": "application/json", "base": "application/json",
"type": "application/vnd.openstack.compute+json;version=3", "type": "application/vnd.openstack.compute+json;version=2.1",
} }
], ],
} }
@ -221,7 +221,7 @@ class Versions(wsgi.Resource):
def __init__(self): def __init__(self):
super(Versions, self).__init__(None) super(Versions, self).__init__(None)
if not CONF.osapi_v3.enabled: if not CONF.osapi_v3.enabled:
del VERSIONS["v3.0"] del VERSIONS["v2.1"]
@wsgi.serializers(xml=VersionsTemplate, @wsgi.serializers(xml=VersionsTemplate,
atom=VersionsAtomSerializer) atom=VersionsAtomSerializer)

View File

@ -1,146 +0,0 @@
# Copyright 2013 IBM Corp.
# Copyright 2010-2011 OpenStack Foundation
#
# 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 webob
from nova.openstack.common import jsonutils
from nova import test
from nova.tests.api.openstack import fakes
NS = {
'atom': 'http://www.w3.org/2005/Atom',
'ns': 'http://docs.openstack.org/common/api/v1.0'
}
EXP_LINKS = {
'v2.0': {
'html': 'http://docs.openstack.org/'
},
'v3.0': {
'html': 'http://docs.openstack.org/'
},
}
EXP_VERSIONS = {
"v2.0": {
"id": "v2.0",
"status": "CURRENT",
"updated": "2011-01-21T11:33:21Z",
"links": [
{
"rel": "self",
"href": "http://localhost/v3/",
},
{
"rel": "describedby",
"type": "text/html",
"href": EXP_LINKS['v2.0']['html'],
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.compute+xml;version=2",
},
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=2",
}
],
},
"v3.0": {
"id": "v3.0",
"status": "EXPERIMENTAL",
"updated": "2013-07-23T11:33:21Z",
"links": [
{
"rel": "self",
"href": "http://localhost/v3/",
},
{
"rel": "describedby",
"type": "text/html",
"href": EXP_LINKS['v3.0']['html'],
},
],
"media-types": [
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=3",
}
],
}
}
class VersionsTest(test.NoDBTestCase):
def test_get_version_list_302(self):
req = webob.Request.blank('/v3')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 302)
redirect_req = webob.Request.blank('/v3/')
self.assertEqual(res.location, redirect_req.url)
def test_get_version_3_detail(self):
req = webob.Request.blank('/v3/')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": EXP_VERSIONS['v3.0']}
self.assertEqual(expected, version)
def test_get_version_3_versions_v3_detail(self):
req = webob.Request.blank('/v3/versions/v3.0')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": EXP_VERSIONS['v3.0']}
self.assertEqual(expected, version)
def test_get_version_3_versions_v2_detail(self):
req = webob.Request.blank('/v3/versions/v2.0')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": EXP_VERSIONS['v2.0']}
self.assertEqual(expected, version)
def test_get_version_3_versions_invalid(self):
req = webob.Request.blank('/v3/versions/1234')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 404)
self.assertEqual(res.content_type, "application/json")
def test_get_version_3_detail_content_type(self):
req = webob.Request.blank('/')
req.accept = "application/json;version=3"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": EXP_VERSIONS['v3.0']}
self.assertEqual(expected, version)

View File

@ -117,54 +117,54 @@ class UrlmapTest(test.NoDBTestCase):
self.assertEqual(body['image']['id'], self.assertEqual(body['image']['id'],
'cedef40a-ed67-4d10-800e-17455edce175') 'cedef40a-ed67-4d10-800e-17455edce175')
def test_path_version_v3(self): def test_path_version_v21(self):
# Test URL path specifying v3 returns v3 content. # Test URL path specifying v2.1 returns v2.1 content.
req = webob.Request.blank('/v3/') req = webob.Request.blank('/v2.1/')
req.accept = "application/json" req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v3(init_only=('versions',))) res = req.get_response(fakes.wsgi_app_v21(init_only=('versions',)))
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json") self.assertEqual(res.content_type, "application/json")
body = jsonutils.loads(res.body) body = jsonutils.loads(res.body)
self.assertEqual(body['version']['id'], 'v3.0') self.assertEqual(body['version']['id'], 'v2.1')
def test_content_type_version_v3(self): def test_content_type_version_v21(self):
# Test Content-Type specifying v3 returns v3 content. # Test Content-Type specifying v2.1 returns v2 content.
req = webob.Request.blank('/') req = webob.Request.blank('/')
req.content_type = "application/json;version=3" req.content_type = "application/json;version=2.1"
req.accept = "application/json" req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v3(init_only=('versions',))) res = req.get_response(fakes.wsgi_app_v21(init_only=('versions',)))
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json") self.assertEqual(res.content_type, "application/json")
body = jsonutils.loads(res.body) body = jsonutils.loads(res.body)
self.assertEqual(body['version']['id'], 'v3.0') self.assertEqual(body['version']['id'], 'v2.1')
def test_accept_version_v3(self): def test_accept_version_v21(self):
# Test Accept header specifying v3 returns v3 content. # Test Accept header specifying v2.1 returns v2.1 content.
req = webob.Request.blank('/') req = webob.Request.blank('/')
req.accept = "application/json;version=3" req.accept = "application/json;version=2.1"
res = req.get_response(fakes.wsgi_app_v3(init_only=('versions',))) res = req.get_response(fakes.wsgi_app_v21(init_only=('versions',)))
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json") self.assertEqual(res.content_type, "application/json")
body = jsonutils.loads(res.body) body = jsonutils.loads(res.body)
self.assertEqual(body['version']['id'], 'v3.0') self.assertEqual(body['version']['id'], 'v2.1')
def test_path_content_type_v3(self): def test_path_content_type_v21(self):
# Test URL path specifying JSON returns JSON content. # Test URL path specifying JSON returns JSON content.
url = '/v3/extensions/extensions.json' url = '/v2.1/fake/extensions/extensions.json'
req = webob.Request.blank(url) req = webob.Request.blank(url)
req.accept = "application/xml" req.accept = "application/xml"
res = req.get_response(fakes.wsgi_app_v3()) res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json") self.assertEqual(res.content_type, "application/json")
body = jsonutils.loads(res.body) body = jsonutils.loads(res.body)
self.assertEqual(body['extension']['name'], 'Extensions') self.assertEqual(body['extension']['name'], 'Extensions')
def test_accept_content_type_v3(self): def test_accept_content_type_v21(self):
# Test Accept header specifying JSON returns JSON content. # Test Accept header specifying JSON returns JSON content.
url = '/v3/extensions/extensions' url = '/v2.1/fake/extensions/extensions'
req = webob.Request.blank(url) req = webob.Request.blank(url)
req.accept = "application/xml;q=0.8, application/json" req.accept = "application/xml;q=0.8, application/json"
res = req.get_response(fakes.wsgi_app_v3(init_only=('extensions',))) res = req.get_response(fakes.wsgi_app_v21(init_only=('extensions',)))
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json") self.assertEqual(res.content_type, "application/json")
body = jsonutils.loads(res.body) body = jsonutils.loads(res.body)

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import uuid as stdlib_uuid import uuid as stdlib_uuid
import feedparser import feedparser
@ -39,6 +40,9 @@ EXP_LINKS = {
'v2.0': { 'v2.0': {
'html': 'http://docs.openstack.org/', 'html': 'http://docs.openstack.org/',
}, },
'v2.1': {
'html': 'http://docs.openstack.org/'
},
} }
@ -65,21 +69,32 @@ EXP_VERSIONS = {
}, },
], ],
}, },
"v3.0": { "v2.1": {
"id": "v3.0", "id": "v2.1",
"status": "EXPERIMENTAL", "status": "EXPERIMENTAL",
"updated": "2013-07-23T11:33:21Z", "updated": "2013-07-23T11:33:21Z",
"links": [
{
"rel": "self",
"href": "http://localhost/v2.1/",
},
{
"rel": "describedby",
"type": "text/html",
"href": EXP_LINKS['v2.1']['html'],
},
],
"media-types": [ "media-types": [
{ {
"base": "application/json", "base": "application/json",
"type": "application/vnd.openstack.compute+json;version=3", "type": "application/vnd.openstack.compute+json;version=2.1",
} }
], ],
} }
} }
class VersionsTest(test.NoDBTestCase): class VersionsTestV20(test.NoDBTestCase):
def test_get_version_list(self): def test_get_version_list(self):
req = webob.Request.blank('/') req = webob.Request.blank('/')
@ -100,13 +115,13 @@ class VersionsTest(test.NoDBTestCase):
}], }],
}, },
{ {
"id": "v3.0", "id": "v2.1",
"status": "EXPERIMENTAL", "status": "EXPERIMENTAL",
"updated": "2013-07-23T11:33:21Z", "updated": "2013-07-23T11:33:21Z",
"links": [ "links": [
{ {
"rel": "self", "rel": "self",
"href": "http://localhost/v3/", "href": "http://localhost/v2/",
}], }],
}, },
] ]
@ -211,7 +226,7 @@ class VersionsTest(test.NoDBTestCase):
versions = root.xpath('ns:version', namespaces=NS) versions = root.xpath('ns:version', namespaces=NS)
self.assertEqual(len(versions), 2) self.assertEqual(len(versions), 2)
for i, v in enumerate(['v2.0', 'v3.0']): for i, v in enumerate(['v2.0', 'v2.1']):
version = versions[i] version = versions[i]
expected = EXP_VERSIONS[v] expected = EXP_VERSIONS[v]
for key in ['id', 'status', 'updated']: for key in ['id', 'status', 'updated']:
@ -285,14 +300,14 @@ class VersionsTest(test.NoDBTestCase):
self.assertEqual(entry.links[0]['rel'], 'self') self.assertEqual(entry.links[0]['rel'], 'self')
entry = f.entries[1] entry = f.entries[1]
self.assertEqual(entry.id, 'http://localhost/v3/') self.assertEqual(entry.id, 'http://localhost/v2/')
self.assertEqual(entry.title, 'Version v3.0') self.assertEqual(entry.title, 'Version v2.1')
self.assertEqual(entry.updated, '2013-07-23T11:33:21Z') self.assertEqual(entry.updated, '2013-07-23T11:33:21Z')
self.assertEqual(len(entry.content), 1) self.assertEqual(len(entry.content), 1)
self.assertEqual(entry.content[0].value, self.assertEqual(entry.content[0].value,
'Version v3.0 EXPERIMENTAL (2013-07-23T11:33:21Z)') 'Version v2.1 EXPERIMENTAL (2013-07-23T11:33:21Z)')
self.assertEqual(len(entry.links), 1) self.assertEqual(len(entry.links), 1)
self.assertEqual(entry.links[0]['href'], 'http://localhost/v3/') self.assertEqual(entry.links[0]['href'], 'http://localhost/v2/')
self.assertEqual(entry.links[0]['rel'], 'self') self.assertEqual(entry.links[0]['rel'], 'self')
def test_multi_choice_image(self): def test_multi_choice_image(self):
@ -327,11 +342,11 @@ class VersionsTest(test.NoDBTestCase):
], ],
}, },
{ {
"id": "v3.0", "id": "v2.1",
"status": "EXPERIMENTAL", "status": "EXPERIMENTAL",
"links": [ "links": [
{ {
"href": "http://localhost/v3/images/1", "href": "http://localhost/v2/images/1",
"rel": "self", "rel": "self",
}, },
], ],
@ -339,7 +354,7 @@ class VersionsTest(test.NoDBTestCase):
{ {
"base": "application/json", "base": "application/json",
"type": "type":
"application/vnd.openstack.compute+json;version=3", "application/vnd.openstack.compute+json;version=2.1",
} }
], ],
}, },
@ -375,18 +390,18 @@ class VersionsTest(test.NoDBTestCase):
[{'rel': 'self', 'href': 'http://localhost/v2/images/1'}])) [{'rel': 'self', 'href': 'http://localhost/v2/images/1'}]))
version = versions[1] version = versions[1]
self.assertEqual(version.get('id'), 'v3.0') self.assertEqual(version.get('id'), 'v2.1')
self.assertEqual(version.get('status'), 'EXPERIMENTAL') self.assertEqual(version.get('status'), 'EXPERIMENTAL')
media_types = version.xpath('ns:media-types/ns:media-type', media_types = version.xpath('ns:media-types/ns:media-type',
namespaces=NS) namespaces=NS)
self.assertTrue(common. self.assertTrue(common.
compare_media_types(media_types, compare_media_types(media_types,
EXP_VERSIONS['v3.0']['media-types'] EXP_VERSIONS['v2.1']['media-types']
)) ))
links = version.xpath('atom:link', namespaces=NS) links = version.xpath('atom:link', namespaces=NS)
self.assertTrue(common.compare_links(links, self.assertTrue(common.compare_links(links,
[{'rel': 'self', 'href': 'http://localhost/v3/images/1'}])) [{'rel': 'self', 'href': 'http://localhost/v2/images/1'}]))
def test_multi_choice_server_atom(self): def test_multi_choice_server_atom(self):
"""Make sure multi choice responses do not have content-type """Make sure multi choice responses do not have content-type
@ -431,11 +446,11 @@ class VersionsTest(test.NoDBTestCase):
], ],
}, },
{ {
"id": "v3.0", "id": "v2.1",
"status": "EXPERIMENTAL", "status": "EXPERIMENTAL",
"links": [ "links": [
{ {
"href": "http://localhost/v3/servers/" + uuid, "href": "http://localhost/v2/servers/" + uuid,
"rel": "self", "rel": "self",
}, },
], ],
@ -443,7 +458,7 @@ class VersionsTest(test.NoDBTestCase):
{ {
"base": "application/json", "base": "application/json",
"type": "type":
"application/vnd.openstack.compute+json;version=3", "application/vnd.openstack.compute+json;version=2.1",
} }
], ],
}, },
@ -496,13 +511,13 @@ class VersionsViewBuilderTests(test.NoDBTestCase):
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
def test_generate_href_v3(self): def test_generate_href_v21(self):
base_url = "http://example.org/app/" base_url = "http://example.org/app/"
expected = "http://example.org/app/v3/" expected = "http://example.org/app/v2/"
builder = views.versions.ViewBuilder(base_url) builder = views.versions.ViewBuilder(base_url)
actual = builder.generate_href('v3.0') actual = builder.generate_href('v2.1')
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ -717,3 +732,66 @@ class VersionsSerializerTests(test.NoDBTestCase):
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
self.assertEqual("application/json", res.content_type) self.assertEqual("application/json", res.content_type)
# NOTE(oomichi): Now version API of v2.0 covers "/"(root).
# So this class tests "/v2.1" only for v2.1 API.
class VersionsTestV21(test.NoDBTestCase):
exp_versions = copy.deepcopy(EXP_VERSIONS)
exp_versions['v2.0']['links'].insert(0,
{'href': 'http://localhost/v2.1/', 'rel': 'self'},
)
def test_get_version_list_302(self):
req = webob.Request.blank('/v2.1')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(res.status_int, 302)
redirect_req = webob.Request.blank('/v2.1/')
self.assertEqual(res.location, redirect_req.url)
def test_get_version_21_detail(self):
req = webob.Request.blank('/v2.1/')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": self.exp_versions['v2.1']}
self.assertEqual(expected, version)
def test_get_version_21_versions_v21_detail(self):
req = webob.Request.blank('/v2.1/fake/versions/v2.1')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": self.exp_versions['v2.1']}
self.assertEqual(expected, version)
def test_get_version_21_versions_v20_detail(self):
req = webob.Request.blank('/v2.1/fake/versions/v2.0')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": self.exp_versions['v2.0']}
self.assertEqual(expected, version)
def test_get_version_21_versions_invalid(self):
req = webob.Request.blank('/v2.1/versions/1234')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(res.status_int, 404)
def test_get_version_21_detail_content_type(self):
req = webob.Request.blank('/')
req.accept = "application/json;version=2.1"
res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/json")
version = jsonutils.loads(res.body)
expected = {"version": self.exp_versions['v2.1']}
self.assertEqual(expected, version)

View File

@ -12,10 +12,10 @@
"updated": "2011-01-21T11:33:21Z" "updated": "2011-01-21T11:33:21Z"
}, },
{ {
"id": "v3.0", "id": "v2.1",
"links": [ "links": [
{ {
"href": "http://openstack.example.com/v3/", "href": "http://openstack.example.com/v2/",
"rel": "self" "rel": "self"
} }
], ],

View File

@ -3,7 +3,7 @@
<version status="CURRENT" updated="2011-01-21T11:33:21Z" id="v2.0"> <version status="CURRENT" updated="2011-01-21T11:33:21Z" id="v2.0">
<atom:link href="http://openstack.example.com/v2/" rel="self"/> <atom:link href="http://openstack.example.com/v2/" rel="self"/>
</version> </version>
<version status="EXPERIMENTAL" updated="2013-07-23T11:33:21Z" id="v3.0"> <version status="EXPERIMENTAL" updated="2013-07-23T11:33:21Z" id="v2.1">
<atom:link href="http://openstack.example.com/v3/" rel="self"/> <atom:link href="http://openstack.example.com/v2/" rel="self"/>
</version> </version>
</versions> </versions>