diff --git a/doc/api_samples/versions-get-resp.json b/doc/api_samples/versions-get-resp.json
index 8bcc7f4f2075..40c7c4add158 100644
--- a/doc/api_samples/versions-get-resp.json
+++ b/doc/api_samples/versions-get-resp.json
@@ -10,6 +10,17 @@
],
"status": "CURRENT",
"updated": "2011-01-21T11:33:21Z"
+ },
+ {
+ "id": "v3.0",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v3/",
+ "rel": "self"
+ }
+ ],
+ "status": "EXPERIMENTAL",
+ "updated": "2013-07-23T11:33:21Z"
}
]
-}
\ No newline at end of file
+}
diff --git a/doc/api_samples/versions-get-resp.xml b/doc/api_samples/versions-get-resp.xml
index d0cea0cd55de..cf53b0dd2c07 100644
--- a/doc/api_samples/versions-get-resp.xml
+++ b/doc/api_samples/versions-get-resp.xml
@@ -3,4 +3,7 @@
-
\ No newline at end of file
+
+
+
+
diff --git a/nova/api/openstack/compute/plugins/v3/versions.py b/nova/api/openstack/compute/plugins/v3/versions.py
new file mode 100644
index 000000000000..8e8f5d71b63e
--- /dev/null
+++ b/nova/api/openstack/compute/plugins/v3/versions.py
@@ -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("", "/")
diff --git a/nova/api/openstack/compute/versions.py b/nova/api/openstack/compute/versions.py
index 0d2117053834..b93287bb6878 100644
--- a/nova/api/openstack/compute/versions.py
+++ b/nova/api/openstack/compute/versions.py
@@ -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-23T11: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)
diff --git a/nova/api/openstack/compute/views/versions.py b/nova/api/openstack/compute/views/versions.py
index 7e8d15f0bbd0..336008c7ac56 100644
--- a/nova/api/openstack/compute/views/versions.py
+++ b/nova/api/openstack/compute/views/versions.py
@@ -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)
diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_versions.py b/nova/tests/api/openstack/compute/plugins/v3/test_versions.py
new file mode 100644
index 000000000000..c0ec034b2931
--- /dev/null
+++ b/nova/tests/api/openstack/compute/plugins/v3/test_versions.py
@@ -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-23T11: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-23T11: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-23T11: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-23T11: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-23T11:33:21Z')
+ self.assertEqual(len(entry.content), 1)
+ self.assertEqual(entry.content[0].value,
+ 'Version v3.0 EXPERIMENTAL (2013-07-23T11: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'})
diff --git a/nova/tests/api/openstack/compute/test_versions.py b/nova/tests/api/openstack/compute/test_versions.py
index 973fd0105f22..66699aa8e118 100644
--- a/nova/tests/api/openstack/compute/test_versions.py
+++ b/nova/tests/api/openstack/compute/test_versions.py
@@ -75,6 +75,21 @@ EXP_VERSIONS = {
},
],
},
+ "v3.0": {
+ "id": "v3.0",
+ "status": "EXPERIMENTAL",
+ "updated": "2013-07-23T11: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-23T11: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-23T11:33:21Z')
+ self.assertEqual(len(entry.content), 1)
+ self.assertEqual(entry.content[0].value,
+ 'Version v3.0 EXPERIMENTAL (2013-07-23T11: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)
diff --git a/nova/tests/integrated/api_samples/versions-get-resp.json.tpl b/nova/tests/integrated/api_samples/versions-get-resp.json.tpl
index b5b20d6b9d28..40c7c4add158 100644
--- a/nova/tests/integrated/api_samples/versions-get-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/versions-get-resp.json.tpl
@@ -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-21T11:33:21Z"
+ },
+ {
+ "id": "v3.0",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v3/",
+ "rel": "self"
+ }
+ ],
+ "status": "EXPERIMENTAL",
+ "updated": "2013-07-23T11:33:21Z"
}
]
}
diff --git a/nova/tests/integrated/api_samples/versions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/versions-get-resp.xml.tpl
index b426f9521521..cf53b0dd2c07 100644
--- a/nova/tests/integrated/api_samples/versions-get-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/versions-get-resp.xml.tpl
@@ -3,4 +3,7 @@
+
+
+
diff --git a/setup.cfg b/setup.cfg
index df7d33995869..3ebfa9988f32 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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