Merge "Adds API version discovery support for V3"

This commit is contained in:
Jenkins
2013年09月02日 10:34:02 +00:00
committed by Gerrit Code Review

View File

@@ -10,6 +10,17 @@
],
"status": "CURRENT",
"updated": "2011年01月21日T11:33:21Z"
},
{
"id": "v3.0",
"links": [
{
"href": "http://openstack.example.com/v3/",
"rel": "self"
}
],
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z"
}
]
}
}

View File

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

View File

@@ -0,0 +1,57 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# 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.
from nova.api.openstack.compute import versions
from nova.api.openstack.compute.views import versions as views_versions
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
ALIAS = "versions"
class VersionsController(object):
@extensions.expected_errors(())
@wsgi.serializers(xml=versions.VersionTemplate,
atom=versions.VersionAtomSerializer)
def show(self, req):
builder = views_versions.get_view_builder(req)
return builder.build_version(versions.VERSIONS['v3.0'])
class Versions(extensions.V3APIExtensionBase):
"""API Version information."""
name = "Versions"
alias = ALIAS
namespace = "http://docs.openstack.org/compute/core/versions/v3"
version = 1
def get_resources(self):
resources = [
extensions.ResourceExtension(ALIAS, VersionsController(),
custom_routes_fn=self.version_map)]
return resources
def get_controller_extensions(self):
return []
def version_map(self, mapper, wsgi_resource):
mapper.connect("versions", "/",
controller=wsgi_resource,
action='show', conditions={"method": ['GET']})
mapper.redirect("", "/")

View File

@@ -16,6 +16,7 @@
# under the License.
from lxml import etree
from oslo.config import cfg
from nova.api.openstack.compute.views import versions as views_versions
from nova.api.openstack import wsgi
@@ -23,6 +24,9 @@ from nova.api.openstack import xmlutil
from nova.openstack.common import timeutils
CONF = cfg.CONF
CONF.import_opt('enabled', 'nova.api.openstack', group='osapi_v3')
LINKS = {
'v2.0': {
'pdf': 'http://docs.openstack.org/'
@@ -30,6 +34,12 @@ LINKS = {
'wadl': 'http://docs.openstack.org/'
'api/openstack-compute/2/wadl/os-compute-2.wadl'
},
'v3.0': {
'pdf': 'http://docs.openstack.org/'
'api/openstack-compute/3/os-compute-devguide-3.pdf',
'wadl': 'http://docs.openstack.org/'
'api/openstack-compute/3/wadl/os-compute-3.wadl'
},
}
@@ -60,6 +70,33 @@ VERSIONS = {
"type": "application/vnd.openstack.compute+json;version=2",
}
],
},
"v3.0": {
"id": "v3.0",
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z",
"links": [
{
"rel": "describedby",
"type": "application/pdf",
"href": LINKS['v3.0']['pdf'],
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": LINKS['v3.0']['wadl'],
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.compute+xml;version=3",
},
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=3",
}
],
}
}
@@ -205,6 +242,8 @@ class VersionAtomSerializer(AtomSerializer):
class Versions(wsgi.Resource):
def __init__(self):
super(Versions, self).__init__(None)
if not CONF.osapi_v3.enabled:
del VERSIONS["v3.0"]
@wsgi.serializers(xml=VersionsTemplate,
atom=VersionsAtomSerializer)

View File

@@ -44,7 +44,7 @@ class ViewBuilder(common.ViewBuilder):
"links": [
{
"rel": "self",
"href": self.generate_href(req.path),
"href": self.generate_href(version['id'], req.path),
},
],
"media-types": version['media-types'],
@@ -75,7 +75,7 @@ class ViewBuilder(common.ViewBuilder):
def _build_links(self, version_data):
"""Generate a container of links that refer to the provided version."""
href = self.generate_href()
href = self.generate_href(version_data['id'])
links = [
{
@@ -86,10 +86,14 @@ class ViewBuilder(common.ViewBuilder):
return links
def generate_href(self, path=None):
def generate_href(self, version, path=None):
"""Create an url that refers to a specific version_number."""
prefix = self._update_compute_link_prefix(self.base_url)
version_number = 'v2'
if version.find('v3.') == 0:
version_number = 'v3'
else:
version_number = 'v2'
if path:
path = path.strip('/')
return os.path.join(prefix, version_number, path)

View File

@@ -0,0 +1,246 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 feedparser
from lxml import etree
import webob
from nova.api.openstack import xmlutil
from nova.openstack.common import jsonutils
from nova import test
from nova.tests.api.openstack import common
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 = {
'v3.0': {
'pdf': 'http://docs.openstack.org/'
'api/openstack-compute/3/os-compute-devguide-3.pdf',
'wadl': 'http://docs.openstack.org/'
'api/openstack-compute/3/wadl/os-compute-3.wadl'
},
}
EXP_VERSIONS = {
"v3.0": {
"id": "v3.0",
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z",
"links": [
{
"rel": "describedby",
"type": "application/pdf",
"href": EXP_LINKS['v3.0']['pdf'],
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": EXP_LINKS['v3.0']['wadl'],
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.compute+xml;version=3",
},
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=3",
}
],
}
}
class VersionsTest(test.TestCase):
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": {
"id": "v3.0",
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z",
"links": [
{
"rel": "self",
"href": "http://localhost/v3/",
},
],
"links": [
{
"rel": "self",
"href": "http://localhost/v3/",
},
{
"rel": "describedby",
"type": "application/pdf",
"href": EXP_LINKS['v3.0']['pdf'],
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": EXP_LINKS['v3.0']['wadl'],
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/"
"vnd.openstack.compute+xml;version=3",
},
{
"base": "application/json",
"type": "application/"
"vnd.openstack.compute+json;version=3",
},
],
},
}
self.assertEqual(expected, version)
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": {
"id": "v3.0",
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z",
"links": [
{
"rel": "self",
"href": "http://localhost/v3/",
},
],
"links": [
{
"rel": "self",
"href": "http://localhost/v3/",
},
{
"rel": "describedby",
"type": "application/pdf",
"href": EXP_LINKS['v3.0']['pdf'],
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": EXP_LINKS['v3.0']['wadl'],
},
],
"media-types": [
{
"base": "application/xml",
"type": "application/"
"vnd.openstack.compute+xml;version=3",
},
{
"base": "application/json",
"type": "application/"
"vnd.openstack.compute+json;version=3",
},
],
},
}
self.assertEqual(expected, version)
def test_get_version_3_detail_xml(self):
req = webob.Request.blank('/v3/')
req.accept = "application/xml"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 200)
self.assertEqual(res.content_type, "application/xml")
version = etree.XML(res.body)
xmlutil.validate_schema(version, 'version')
expected = EXP_VERSIONS['v3.0']
self.assertTrue(version.xpath('/ns:version', namespaces=NS))
media_types = version.xpath('ns:media-types/ns:media-type',
namespaces=NS)
self.assertTrue(common.compare_media_types(media_types,
expected['media-types']))
for key in ['id', 'status', 'updated']:
self.assertEqual(version.get(key), expected[key])
links = version.xpath('atom:link', namespaces=NS)
self.assertTrue(common.compare_links(links,
[{'rel': 'self', 'href': 'http://localhost/v3/'}]
+ expected['links']))
def test_get_version_3_detail_atom(self):
req = webob.Request.blank('/v3/')
req.accept = "application/atom+xml"
res = req.get_response(fakes.wsgi_app_v3())
self.assertEqual(res.status_int, 200)
self.assertEqual("application/atom+xml", res.content_type)
xmlutil.validate_schema(etree.XML(res.body), 'atom')
f = feedparser.parse(res.body)
self.assertEqual(f.feed.title, 'About This Version')
self.assertEqual(f.feed.updated, '2013年07月23日T11:33:21Z')
self.assertEqual(f.feed.id, 'http://localhost/v3/')
self.assertEqual(f.feed.author, 'Rackspace')
self.assertEqual(f.feed.author_detail.href,
'http://www.rackspace.com/')
self.assertEqual(f.feed.links[0]['href'], 'http://localhost/v3/')
self.assertEqual(f.feed.links[0]['rel'], 'self')
self.assertEqual(len(f.entries), 1)
entry = f.entries[0]
self.assertEqual(entry.id, 'http://localhost/v3/')
self.assertEqual(entry.title, 'Version v3.0')
self.assertEqual(entry.updated, '2013年07月23日T11:33:21Z')
self.assertEqual(len(entry.content), 1)
self.assertEqual(entry.content[0].value,
'Version v3.0 EXPERIMENTAL (2013年07月23日T11:33:21Z)')
self.assertEqual(len(entry.links), 3)
self.assertEqual(entry.links[0]['href'], 'http://localhost/v3/')
self.assertEqual(entry.links[0]['rel'], 'self')
self.assertEqual(entry.links[1], {
'href': EXP_LINKS['v3.0']['pdf'],
'type': 'application/pdf',
'rel': 'describedby'})
self.assertEqual(entry.links[2], {
'href': EXP_LINKS['v3.0']['wadl'],
'type': 'application/vnd.sun.wadl+xml',
'rel': 'describedby'})

View File

@@ -75,6 +75,21 @@ EXP_VERSIONS = {
},
],
},
"v3.0": {
"id": "v3.0",
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z",
"media-types": [
{
"base": "application/xml",
"type": "application/vnd.openstack.compute+xml;version=3",
},
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=3",
}
],
}
}
@@ -98,6 +113,16 @@ class VersionsTest(test.TestCase):
"href": "http://localhost/v2/",
}],
},
{
"id": "v3.0",
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z",
"links": [
{
"rel": "self",
"href": "http://localhost/v3/",
}],
},
]
self.assertEqual(versions, expected)
@@ -232,9 +257,9 @@ class VersionsTest(test.TestCase):
self.assertTrue(root.xpath('/ns:versions', namespaces=NS))
versions = root.xpath('ns:version', namespaces=NS)
self.assertEqual(len(versions), 1)
self.assertEqual(len(versions), 2)
for i, v in enumerate(['v2.0']):
for i, v in enumerate(['v2.0', 'v3.0']):
version = versions[i]
expected = EXP_VERSIONS[v]
for key in ['id', 'status', 'updated']:
@@ -291,7 +316,7 @@ class VersionsTest(test.TestCase):
f = feedparser.parse(res.body)
self.assertEqual(f.feed.title, 'Available API Versions')
self.assertEqual(f.feed.updated, '2011-01-21T11:33:21Z')
self.assertEqual(f.feed.updated, '2013-07-23T11:33:21Z')
self.assertEqual(f.feed.id, 'http://localhost/')
self.assertEqual(f.feed.author, 'Rackspace')
self.assertEqual(f.feed.author_detail.href,
@@ -299,7 +324,7 @@ class VersionsTest(test.TestCase):
self.assertEqual(f.feed.links[0]['href'], 'http://localhost/')
self.assertEqual(f.feed.links[0]['rel'], 'self')
self.assertEqual(len(f.entries), 1)
self.assertEqual(len(f.entries), 2)
entry = f.entries[0]
self.assertEqual(entry.id, 'http://localhost/v2/')
self.assertEqual(entry.title, 'Version v2.0')
@@ -311,6 +336,17 @@ class VersionsTest(test.TestCase):
self.assertEqual(entry.links[0]['href'], 'http://localhost/v2/')
self.assertEqual(entry.links[0]['rel'], 'self')
entry = f.entries[1]
self.assertEqual(entry.id, 'http://localhost/v3/')
self.assertEqual(entry.title, 'Version v3.0')
self.assertEqual(entry.updated, '2013年07月23日T11:33:21Z')
self.assertEqual(len(entry.content), 1)
self.assertEqual(entry.content[0].value,
'Version v3.0 EXPERIMENTAL (2013年07月23日T11:33:21Z)')
self.assertEqual(len(entry.links), 1)
self.assertEqual(entry.links[0]['href'], 'http://localhost/v3/')
self.assertEqual(entry.links[0]['rel'], 'self')
def test_multi_choice_image(self):
req = webob.Request.blank('/images/1')
req.accept = "application/json"
@@ -320,6 +356,28 @@ class VersionsTest(test.TestCase):
expected = {
"choices": [
{
"id": "v3.0",
"status": "EXPERIMENTAL",
"links": [
{
"href": "http://localhost/v3/images/1",
"rel": "self",
},
],
"media-types": [
{
"base": "application/xml",
"type":
"application/vnd.openstack.compute+xml;version=3",
},
{
"base": "application/json",
"type":
"application/vnd.openstack.compute+json;version=3",
}
],
},
{
"id": "v2.0",
"status": "CURRENT",
@@ -357,9 +415,9 @@ class VersionsTest(test.TestCase):
root = etree.XML(res.body)
self.assertTrue(root.xpath('/ns:choices', namespaces=NS))
versions = root.xpath('ns:version', namespaces=NS)
self.assertEqual(len(versions), 1)
self.assertEqual(len(versions), 2)
version = versions[0]
version = versions[1]
self.assertEqual(version.get('id'), 'v2.0')
self.assertEqual(version.get('status'), 'CURRENT')
media_types = version.xpath('ns:media-types/ns:media-type',
@@ -373,6 +431,20 @@ class VersionsTest(test.TestCase):
self.assertTrue(common.compare_links(links,
[{'rel': 'self', 'href': 'http://localhost/v2/images/1'}]))
version = versions[0]
self.assertEqual(version.get('id'), 'v3.0')
self.assertEqual(version.get('status'), 'EXPERIMENTAL')
media_types = version.xpath('ns:media-types/ns:media-type',
namespaces=NS)
self.assertTrue(common.
compare_media_types(media_types,
EXP_VERSIONS['v3.0']['media-types']
))
links = version.xpath('atom:link', namespaces=NS)
self.assertTrue(common.compare_links(links,
[{'rel': 'self', 'href': 'http://localhost/v3/images/1'}]))
def test_multi_choice_server_atom(self):
"""
Make sure multi choice responses do not have content-type
@@ -394,6 +466,28 @@ class VersionsTest(test.TestCase):
expected = {
"choices": [
{
"id": "v3.0",
"status": "EXPERIMENTAL",
"links": [
{
"href": "http://localhost/v3/servers/" + uuid,
"rel": "self",
},
],
"media-types": [
{
"base": "application/xml",
"type":
"application/vnd.openstack.compute+xml;version=3",
},
{
"base": "application/json",
"type":
"application/vnd.openstack.compute+json;version=3",
}
],
},
{
"id": "v2.0",
"status": "CURRENT",
@@ -461,7 +555,27 @@ class VersionsViewBuilderTests(test.TestCase):
expected = "http://example.org/app/v2/"
builder = views.versions.ViewBuilder(base_url)
actual = builder.generate_href()
actual = builder.generate_href('v2')
self.assertEqual(actual, expected)
def test_generate_href_v3(self):
base_url = "http://example.org/app/"
expected = "http://example.org/app/v3/"
builder = views.versions.ViewBuilder(base_url)
actual = builder.generate_href('v3.0')
self.assertEqual(actual, expected)
def test_generate_href_unknown(self):
base_url = "http://example.org/app/"
expected = "http://example.org/app/v2/"
builder = views.versions.ViewBuilder(base_url)
actual = builder.generate_href('foo')
self.assertEqual(actual, expected)

View File

@@ -4,12 +4,23 @@
"id": "v2.0",
"links": [
{
"href": "%(host)s/v2/",
"href": "http://openstack.example.com/v2/",
"rel": "self"
}
],
"status": "CURRENT",
"updated": "2011年01月21日T11:33:21Z"
},
{
"id": "v3.0",
"links": [
{
"href": "http://openstack.example.com/v3/",
"rel": "self"
}
],
"status": "EXPERIMENTAL",
"updated": "2013年07月23日T11:33:21Z"
}
]
}

View File

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

View File

@@ -106,6 +106,7 @@ nova.api.v3.extensions =
services = nova.api.openstack.compute.plugins.v3.services:Services
simple_tenant_usage = nova.api.openstack.compute.plugins.v3.simple_tenant_usage:SimpleTenantUsage
used_limits = nova.api.openstack.compute.plugins.v3.used_limits:UsedLimits
versions = nova.api.openstack.compute.plugins.v3.versions:Versions
nova.api.v3.extensions.server.create =
availability_zone = nova.api.openstack.compute.plugins.v3.availability_zone:AvailabilityZone
Reference in New Issue
openstack/nova
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.

The note is not visible to the blocked user.