Merge lp:~rackspace-titan/nova/versions_detailed_json_xml into lp:~hudson-openstack/nova/trunk

Proposed by William Wolf
Status: Merged
Approved by: Vish Ishaya
Approved revision: 1338
Merged at revision: 1357
Proposed branch: lp:~rackspace-titan/nova/versions_detailed_json_xml
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1479 lines (+1084/-132)
5 files modified
nova/api/openstack/__init__.py (+5/-0)
nova/api/openstack/versions.py (+235/-37)
nova/api/openstack/views/versions.py (+46/-12)
nova/api/openstack/wsgi.py (+3/-0)
nova/tests/api/openstack/test_versions.py (+795/-83)
To merge this branch: bzr merge lp:~rackspace-titan/nova/versions_detailed_json_xml
Reviewer Review Type Date Requested Status
Vish Ishaya (community) Approve
Alex Meade (community) Approve
Brian Waldon (community) Approve
Review via email: mp+69153@code.launchpad.net

Description of the change

Add support for 300 Multiple Choice responses when no version identifier is used in the URI (or no version header is present)

Also adds support for server detail json/xml/atom requests.

To post a comment you must log in.
1327. By William Wolf

merge from trunk

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Can you keep all of the XMLNS constant strings in nova.api.openstack.wsgi?

review: Needs Information
Revision history for this message
William Wolf (throughnothing) wrote :

Thanks Brian, I hadn't realized some of these were already in wsgi. I'll fix that up.

1328. By William Wolf

use wsgi XMLNS/ATOM vars

1329. By William Wolf

refactoring and make self links correct (not hard coded)

Revision history for this message
William Wolf (throughnothing) wrote :

Alright, I think I got the wsgi xmlns variables in use, and fixed up the self link issues.

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Looking good, but you've got one pep8 error.

46, 52, 79, 85: (Minor style nit) If you are going to break up strings like this, can you line them up vertically like this:

51 + "href": "http://docs.rackspacecloud.com/"
52 + "servers/api/v1.0/application.wadl"

103-107: Why did you need to add this method? It already has this method from wsgi.Resource's parent class. I did notice the cls() call is different, can you explain?

462,463: Just a note, the trailing comma is preferred to prevent having to add it when a new item is appended to the list.

Revision history for this message
William Wolf (throughnothing) wrote :

I think you are right about factory() Brian, we don't need it. I didn't realise that wsgi.Application had it already.

I've fixed the pep8 and other issues as well.

1330. By William Wolf

fixed pep8 issues and removed unnecessary factory function

1331. By William Wolf

merge with trunk

1332. By William Wolf

fixed typo

1333. By William Wolf

fix more spacing issues, and removed self link from versions template data

1334. By William Wolf

fix run_tests.sh

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Leave Gabe to me.

review: Approve
1335. By William Wolf

merge from trunk

1336. By William Wolf

merge from trunk

Revision history for this message
Alex Meade (alex-meade) wrote :

Good work nailing this branch down

review: Approve
1337. By William Wolf

merge from trunk

1338. By William Wolf

merge from trunk

Revision history for this message
Vish Ishaya (vishvananda) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-07-22 21:04:26 +0000
+++ nova/api/openstack/__init__.py 2011-08-02 20:52:09 +0000
@@ -40,6 +40,7 @@
40from nova.api.openstack import server_metadata40from nova.api.openstack import server_metadata
41from nova.api.openstack import shared_ip_groups41from nova.api.openstack import shared_ip_groups
42from nova.api.openstack import users42from nova.api.openstack import users
43from nova.api.openstack import versions
43from nova.api.openstack import wsgi44from nova.api.openstack import wsgi
44from nova.api.openstack import zones45from nova.api.openstack import zones
4546
@@ -115,6 +116,10 @@
115 'select': 'POST',116 'select': 'POST',
116 'boot': 'POST'})117 'boot': 'POST'})
117118
119 mapper.connect("versions", "/",
120 controller=versions.create_resource(version),
121 action='show')
122
118 mapper.resource("console", "consoles",123 mapper.resource("console", "consoles",
119 controller=consoles.create_resource(),124 controller=consoles.create_resource(),
120 parent_resource=dict(member_name='server',125 parent_resource=dict(member_name='server',
121126
=== modified file 'nova/api/openstack/versions.py'
--- nova/api/openstack/versions.py 2011-07-21 16:19:09 +0000
+++ nova/api/openstack/versions.py 2011-08-02 20:52:09 +0000
@@ -24,7 +24,66 @@
24from nova.api.openstack import wsgi24from nova.api.openstack import wsgi
2525
2626
27ATOM_XMLNS = "http://www.w3.org/2005/Atom"27VERSIONS = {
28 "v1.0": {
29 "id": "v1.0",
30 "status": "DEPRECATED",
31 "updated": "2011-01-21T11:33:21Z",
32 "links": [
33 {
34 "rel": "describedby",
35 "type": "application/pdf",
36 "href": "http://docs.rackspacecloud.com/"
37 "servers/api/v1.0/cs-devguide-20110125.pdf"
38 },
39 {
40 "rel": "describedby",
41 "type": "application/vnd.sun.wadl+xml",
42 "href": "http://docs.rackspacecloud.com/"
43 "servers/api/v1.0/application.wadl"
44 },
45 ],
46 "media-types": [
47 {
48 "base": "application/xml",
49 "type": "application/vnd.openstack.compute-v1.0+xml"
50 },
51 {
52 "base": "application/json",
53 "type": "application/vnd.openstack.compute-v1.0+json"
54 }
55 ],
56 },
57 "v1.1": {
58 "id": "v1.1",
59 "status": "CURRENT",
60 "updated": "2011-01-21T11:33:21Z",
61 "links": [
62 {
63 "rel": "describedby",
64 "type": "application/pdf",
65 "href": "http://docs.rackspacecloud.com/"
66 "servers/api/v1.1/cs-devguide-20110125.pdf"
67 },
68 {
69 "rel": "describedby",
70 "type": "application/vnd.sun.wadl+xml",
71 "href": "http://docs.rackspacecloud.com/"
72 "servers/api/v1.1/application.wadl"
73 },
74 ],
75 "media-types": [
76 {
77 "base": "application/xml",
78 "type": "application/vnd.openstack.compute-v1.1+xml"
79 },
80 {
81 "base": "application/json",
82 "type": "application/vnd.openstack.compute-v1.1+json"
83 }
84 ],
85 },
86}
2887
2988
30class Versions(wsgi.Resource):89class Versions(wsgi.Resource):
@@ -36,16 +95,20 @@
36 }95 }
37 }96 }
3897
98 headers_serializer = VersionsHeadersSerializer()
99
39 body_serializers = {100 body_serializers = {
40 'application/atom+xml': VersionsAtomSerializer(metadata=metadata),101 'application/atom+xml': VersionsAtomSerializer(metadata=metadata),
41 'application/xml': VersionsXMLSerializer(metadata=metadata),102 'application/xml': VersionsXMLSerializer(metadata=metadata),
42 }103 }
43 serializer = wsgi.ResponseSerializer(body_serializers)104 serializer = wsgi.ResponseSerializer(
105 body_serializers=body_serializers,
106 headers_serializer=headers_serializer)
44107
45 supported_content_types = ('application/json',108 supported_content_types = ('application/json',
46 'application/xml',109 'application/xml',
47 'application/atom+xml')110 'application/atom+xml')
48 deserializer = wsgi.RequestDeserializer(111 deserializer = VersionsRequestDeserializer(
49 supported_content_types=supported_content_types)112 supported_content_types=supported_content_types)
50113
51 wsgi.Resource.__init__(self, None, serializer=serializer,114 wsgi.Resource.__init__(self, None, serializer=serializer,
@@ -53,60 +116,131 @@
53116
54 def dispatch(self, request, *args):117 def dispatch(self, request, *args):
55 """Respond to a request for all OpenStack API versions."""118 """Respond to a request for all OpenStack API versions."""
56 version_objs = [
57 {
58 "id": "v1.1",
59 "status": "CURRENT",
60 #TODO(wwolf) get correct value for these
61 "updated": "2011-07-18T11:30:00Z",
62 },
63 {
64 "id": "v1.0",
65 "status": "DEPRECATED",
66 #TODO(wwolf) get correct value for these
67 "updated": "2010-10-09T11:30:00Z",
68 },
69 ]
70
71 builder = nova.api.openstack.views.versions.get_view_builder(request)119 builder = nova.api.openstack.views.versions.get_view_builder(request)
72 versions = [builder.build(version) for version in version_objs]120 if request.path == '/':
73 return dict(versions=versions)121 # List Versions
122 return builder.build_versions(VERSIONS)
123 else:
124 # Versions Multiple Choice
125 return builder.build_choices(VERSIONS, request)
126
127
128class VersionV10(object):
129 def show(self, req):
130 builder = nova.api.openstack.views.versions.get_view_builder(req)
131 return builder.build_version(VERSIONS['v1.0'])
132
133
134class VersionV11(object):
135 def show(self, req):
136 builder = nova.api.openstack.views.versions.get_view_builder(req)
137 return builder.build_version(VERSIONS['v1.1'])
138
139
140class VersionsRequestDeserializer(wsgi.RequestDeserializer):
141 def get_expected_content_type(self, request):
142 supported_content_types = list(self.supported_content_types)
143 if request.path != '/':
144 # Remove atom+xml accept type for 300 responses
145 if 'application/atom+xml' in supported_content_types:
146 supported_content_types.remove('application/atom+xml')
147
148 return request.best_match_content_type(supported_content_types)
149
150 def get_action_args(self, request_environment):
151 """Parse dictionary created by routes library."""
152 args = {}
153 if request_environment['PATH_INFO'] == '/':
154 args['action'] = 'index'
155 else:
156 args['action'] = 'multi'
157
158 return args
74159
75160
76class VersionsXMLSerializer(wsgi.XMLDictSerializer):161class VersionsXMLSerializer(wsgi.XMLDictSerializer):
77 def _versions_to_xml(self, versions):162 #TODO(wwolf): this is temporary until we get rid of toprettyxml
78 root = self._xml_doc.createElement('versions')163 # in the base class (XMLDictSerializer), which I plan to do in
164 # another branch
165 def to_xml_string(self, node, has_atom=False):
166 self._add_xmlns(node, has_atom)
167 return node.toxml(encoding='UTF-8')
168
169 def _versions_to_xml(self, versions, name="versions", xmlns=None):
170 root = self._xml_doc.createElement(name)
171 root.setAttribute("xmlns", wsgi.XMLNS_V11)
172 root.setAttribute("xmlns:atom", wsgi.XMLNS_ATOM)
79173
80 for version in versions:174 for version in versions:
81 root.appendChild(self._create_version_node(version))175 root.appendChild(self._create_version_node(version))
82176
83 return root177 return root
84178
85 def _create_version_node(self, version):179 def _create_media_types(self, media_types):
180 base = self._xml_doc.createElement('media-types')
181 for type in media_types:
182 node = self._xml_doc.createElement('media-type')
183 node.setAttribute('base', type['base'])
184 node.setAttribute('type', type['type'])
185 base.appendChild(node)
186
187 return base
188
189 def _create_version_node(self, version, create_ns=False):
86 version_node = self._xml_doc.createElement('version')190 version_node = self._xml_doc.createElement('version')
191 if create_ns:
192 xmlns = wsgi.XMLNS_V11
193 xmlns_atom = wsgi.XMLNS_ATOM
194 version_node.setAttribute('xmlns', xmlns)
195 version_node.setAttribute('xmlns:atom', xmlns_atom)
196
87 version_node.setAttribute('id', version['id'])197 version_node.setAttribute('id', version['id'])
88 version_node.setAttribute('status', version['status'])198 version_node.setAttribute('status', version['status'])
89 version_node.setAttribute('updated', version['updated'])199 if 'updated' in version:
90200 version_node.setAttribute('updated', version['updated'])
91 for link in version['links']:201
92 link_node = self._xml_doc.createElement('atom:link')202 if 'media-types' in version:
93 link_node.setAttribute('rel', link['rel'])203 media_types = self._create_media_types(version['media-types'])
94 link_node.setAttribute('href', link['href'])204 version_node.appendChild(media_types)
95 version_node.appendChild(link_node)205
206 link_nodes = self._create_link_nodes(self._xml_doc, version['links'])
207 for link in link_nodes:
208 version_node.appendChild(link)
96209
97 return version_node210 return version_node
98211
99 def default(self, data):212 def index(self, data):
100 self._xml_doc = minidom.Document()213 self._xml_doc = minidom.Document()
101 node = self._versions_to_xml(data['versions'])214 node = self._versions_to_xml(data['versions'])
102215
103 return self.to_xml_string(node)216 return self.to_xml_string(node)
104217
218 def show(self, data):
219 self._xml_doc = minidom.Document()
220 node = self._create_version_node(data['version'], True)
221
222 return self.to_xml_string(node)
223
224 def multi(self, data):
225 self._xml_doc = minidom.Document()
226 node = self._versions_to_xml(data['choices'], 'choices',
227 xmlns=wsgi.XMLNS_V11)
228
229 return self.to_xml_string(node)
230
105231
106class VersionsAtomSerializer(wsgi.XMLDictSerializer):232class VersionsAtomSerializer(wsgi.XMLDictSerializer):
233 #TODO(wwolf): this is temporary until we get rid of toprettyxml
234 # in the base class (XMLDictSerializer), which I plan to do in
235 # another branch
236 def to_xml_string(self, node, has_atom=False):
237 self._add_xmlns(node, has_atom)
238 return node.toxml(encoding='UTF-8')
239
107 def __init__(self, metadata=None, xmlns=None):240 def __init__(self, metadata=None, xmlns=None):
241 self.metadata = metadata or {}
108 if not xmlns:242 if not xmlns:
109 self.xmlns = ATOM_XMLNS243 self.xmlns = wsgi.XMLNS_ATOM
110 else:244 else:
111 self.xmlns = xmlns245 self.xmlns = xmlns
112246
@@ -135,8 +269,33 @@
135 link_href = link_href.rstrip('/')269 link_href = link_href.rstrip('/')
136 return link_href.rsplit('/', 1)[0] + '/'270 return link_href.rsplit('/', 1)[0] + '/'
137271
138 def _create_meta(self, root, versions):272 def _create_detail_meta(self, root, version):
139 title = self._create_text_elem('title', 'Available API Versions',273 title = self._create_text_elem('title', "About This Version",
274 type='text')
275
276 updated = self._create_text_elem('updated', version['updated'])
277
278 uri = version['links'][0]['href']
279 id = self._create_text_elem('id', uri)
280
281 link = self._xml_doc.createElement('link')
282 link.setAttribute('rel', 'self')
283 link.setAttribute('href', uri)
284
285 author = self._xml_doc.createElement('author')
286 author_name = self._create_text_elem('name', 'Rackspace')
287 author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/')
288 author.appendChild(author_name)
289 author.appendChild(author_uri)
290
291 root.appendChild(title)
292 root.appendChild(updated)
293 root.appendChild(id)
294 root.appendChild(author)
295 root.appendChild(link)
296
297 def _create_list_meta(self, root, versions):
298 title = self._create_text_elem('title', "Available API Versions",
140 type='text')299 type='text')
141 # Set this updated to the most recently updated version300 # Set this updated to the most recently updated version
142 recent = self._get_most_recent_update(versions)301 recent = self._get_most_recent_update(versions)
@@ -144,6 +303,7 @@
144303
145 base_url = self._get_base_url(versions[0]['links'][0]['href'])304 base_url = self._get_base_url(versions[0]['links'][0]['href'])
146 id = self._create_text_elem('id', base_url)305 id = self._create_text_elem('id', base_url)
306
147 link = self._xml_doc.createElement('link')307 link = self._xml_doc.createElement('link')
148 link.setAttribute('rel', 'self')308 link.setAttribute('rel', 'self')
149 link.setAttribute('href', base_url)309 link.setAttribute('href', base_url)
@@ -178,7 +338,10 @@
178 link_node = self._xml_doc.createElement('link')338 link_node = self._xml_doc.createElement('link')
179 link_node.setAttribute('rel', link['rel'])339 link_node.setAttribute('rel', link['rel'])
180 link_node.setAttribute('href', link['href'])340 link_node.setAttribute('href', link['href'])
181 entry.appendChild(link_node)341 if 'type' in link:
342 link_node.setAttribute('type', link['type'])
343
344 entry.appendChild(link_node)
182345
183 content = self._create_text_elem('content',346 content = self._create_text_elem('content',
184 'Version %s %s (%s)' %347 'Version %s %s (%s)' %
@@ -190,10 +353,45 @@
190 entry.appendChild(content)353 entry.appendChild(content)
191 root.appendChild(entry)354 root.appendChild(entry)
192355
193 def default(self, data):356 def index(self, data):
194 self._xml_doc = minidom.Document()357 self._xml_doc = minidom.Document()
195 node = self._xml_doc.createElementNS(self.xmlns, 'feed')358 node = self._xml_doc.createElementNS(self.xmlns, 'feed')
196 self._create_meta(node, data['versions'])359 self._create_list_meta(node, data['versions'])
197 self._create_version_entries(node, data['versions'])360 self._create_version_entries(node, data['versions'])
198361
199 return self.to_xml_string(node)362 return self.to_xml_string(node)
363
364 def show(self, data):
365 self._xml_doc = minidom.Document()
366 node = self._xml_doc.createElementNS(self.xmlns, 'feed')
367 self._create_detail_meta(node, data['version'])
368 self._create_version_entries(node, [data['version']])
369
370 return self.to_xml_string(node)
371
372
373class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer):
374 def multi(self, response, data):
375 response.status_int = 300
376
377
378def create_resource(version='1.0'):
379 controller = {
380 '1.0': VersionV10,
381 '1.1': VersionV11,
382 }[version]()
383
384 body_serializers = {
385 'application/xml': VersionsXMLSerializer(),
386 'application/atom+xml': VersionsAtomSerializer(),
387 }
388 serializer = wsgi.ResponseSerializer(body_serializers)
389
390 supported_content_types = ('application/json',
391 'application/xml',
392 'application/atom+xml')
393 deserializer = wsgi.RequestDeserializer(
394 supported_content_types=supported_content_types)
395
396 return wsgi.Resource(controller, serializer=serializer,
397 deserializer=deserializer)
200398
=== modified file 'nova/api/openstack/views/versions.py'
--- nova/api/openstack/views/versions.py 2011-07-20 20:47:17 +0000
+++ nova/api/openstack/views/versions.py 2011-08-02 20:52:09 +0000
@@ -15,6 +15,7 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18import copy
18import os19import os
1920
2021
@@ -31,16 +32,44 @@
31 """32 """
32 self.base_url = base_url33 self.base_url = base_url
3334
34 def build(self, version_data):35 def build_choices(self, VERSIONS, req):
35 """Generic method used to generate a version entity."""36 version_objs = []
36 version = {37 for version in VERSIONS:
37 "id": version_data["id"],38 version = VERSIONS[version]
38 "status": version_data["status"],39 version_objs.append({
39 "updated": version_data["updated"],40 "id": version['id'],
40 "links": self._build_links(version_data),41 "status": version['status'],
41 }42 "links": [
4243 {
43 return version44 "rel": "self",
45 "href": self.generate_href(version['id'], req.path)
46 }
47 ],
48 "media-types": version['media-types']
49 })
50
51 return dict(choices=version_objs)
52
53 def build_versions(self, versions):
54 version_objs = []
55 for version in versions:
56 version = versions[version]
57 version_objs.append({
58 "id": version['id'],
59 "status": version['status'],
60 "updated": version['updated'],
61 "links": self._build_links(version),
62 })
63
64 return dict(versions=version_objs)
65
66 def build_version(self, version):
67 reval = copy.deepcopy(version)
68 reval['links'].insert(0, {
69 "rel": "self",
70 "href": self.base_url.rstrip('/') + '/',
71 })
72 return dict(version=reval)
4473
45 def _build_links(self, version_data):74 def _build_links(self, version_data):
46 """Generate a container of links that refer to the provided version."""75 """Generate a container of links that refer to the provided version."""
@@ -55,6 +84,11 @@
5584
56 return links85 return links
5786
58 def generate_href(self, version_number):87 def generate_href(self, version_number, path=None):
59 """Create an url that refers to a specific version_number."""88 """Create an url that refers to a specific version_number."""
60 return os.path.join(self.base_url, version_number) + '/'89 version_number = version_number.strip('/')
90 if path:
91 path = path.strip('/')
92 return os.path.join(self.base_url, version_number, path)
93 else:
94 return os.path.join(self.base_url, version_number) + '/'
6195
=== modified file 'nova/api/openstack/wsgi.py'
--- nova/api/openstack/wsgi.py 2011-07-28 21:59:25 +0000
+++ nova/api/openstack/wsgi.py 2011-08-02 20:52:09 +0000
@@ -13,6 +13,7 @@
1313
14XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'14XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
15XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1'15XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
16
16XMLNS_ATOM = 'http://www.w3.org/2005/Atom'17XMLNS_ATOM = 'http://www.w3.org/2005/Atom'
1718
18LOG = logging.getLogger('nova.api.openstack.wsgi')19LOG = logging.getLogger('nova.api.openstack.wsgi')
@@ -386,6 +387,8 @@
386 link_node = xml_doc.createElement('atom:link')387 link_node = xml_doc.createElement('atom:link')
387 link_node.setAttribute('rel', link['rel'])388 link_node.setAttribute('rel', link['rel'])
388 link_node.setAttribute('href', link['href'])389 link_node.setAttribute('href', link['href'])
390 if 'type' in link:
391 link_node.setAttribute('type', link['type'])
389 link_nodes.append(link_node)392 link_nodes.append(link_node)
390 return link_nodes393 return link_nodes
391394
392395
=== modified file 'nova/tests/api/openstack/test_versions.py'
--- nova/tests/api/openstack/test_versions.py 2011-07-21 21:20:32 +0000
+++ nova/tests/api/openstack/test_versions.py 2011-08-02 20:52:09 +0000
@@ -16,21 +16,92 @@
16# under the License.16# under the License.
1717
18import json18import json
19import stubout
19import webob20import webob
21import xml.etree.ElementTree
22
2023
21from nova import context24from nova import context
22from nova import test25from nova import test
23from nova.tests.api.openstack import fakes26from nova.tests.api.openstack import fakes
24from nova.api.openstack import versions27from nova.api.openstack import versions
25from nova.api.openstack import views28from nova.api.openstack import views
29from nova.api.openstack import wsgi
30
31VERSIONS = {
32 "v1.0": {
33 "id": "v1.0",
34 "status": "DEPRECATED",
35 "updated": "2011-01-21T11:33:21Z",
36 "links": [
37 {
38 "rel": "describedby",
39 "type": "application/pdf",
40 "href": "http://docs.rackspacecloud.com/"
41 "servers/api/v1.0/cs-devguide-20110125.pdf"
42 },
43 {
44 "rel": "describedby",
45 "type": "application/vnd.sun.wadl+xml",
46 "href": "http://docs.rackspacecloud.com/"
47 "servers/api/v1.0/application.wadl"
48 },
49 ],
50 "media-types": [
51 {
52 "base": "application/xml",
53 "type": "application/vnd.openstack.compute-v1.0+xml"
54 },
55 {
56 "base": "application/json",
57 "type": "application/vnd.openstack.compute-v1.0+json"
58 }
59 ],
60 },
61 "v1.1": {
62 "id": "v1.1",
63 "status": "CURRENT",
64 "updated": "2011-01-21T11:33:21Z",
65 "links": [
66 {
67 "rel": "describedby",
68 "type": "application/pdf",
69 "href": "http://docs.rackspacecloud.com/"
70 "servers/api/v1.1/cs-devguide-20110125.pdf"
71 },
72 {
73 "rel": "describedby",
74 "type": "application/vnd.sun.wadl+xml",
75 "href": "http://docs.rackspacecloud.com/"
76 "servers/api/v1.1/application.wadl"
77 },
78 ],
79 "media-types": [
80 {
81 "base": "application/xml",
82 "type": "application/vnd.openstack.compute-v1.1+xml"
83 },
84 {
85 "base": "application/json",
86 "type": "application/vnd.openstack.compute-v1.1+json"
87 }
88 ],
89 },
90}
2691
2792
28class VersionsTest(test.TestCase):93class VersionsTest(test.TestCase):
29 def setUp(self):94 def setUp(self):
30 super(VersionsTest, self).setUp()95 super(VersionsTest, self).setUp()
31 self.context = context.get_admin_context()96 self.context = context.get_admin_context()
97 self.stubs = stubout.StubOutForTesting()
98 fakes.stub_out_auth(self.stubs)
99 #Stub out VERSIONS
100 self.old_versions = versions.VERSIONS
101 versions.VERSIONS = VERSIONS
32102
33 def tearDown(self):103 def tearDown(self):
104 versions.VERSIONS = self.old_versions
34 super(VersionsTest, self).tearDown()105 super(VersionsTest, self).tearDown()
35106
36 def test_get_version_list(self):107 def test_get_version_list(self):
@@ -44,7 +115,7 @@
44 {115 {
45 "id": "v1.1",116 "id": "v1.1",
46 "status": "CURRENT",117 "status": "CURRENT",
47 "updated": "2011-07-18T11:30:00Z",118 "updated": "2011-01-21T11:33:21Z",
48 "links": [119 "links": [
49 {120 {
50 "rel": "self",121 "rel": "self",
@@ -54,7 +125,7 @@
54 {125 {
55 "id": "v1.0",126 "id": "v1.0",
56 "status": "DEPRECATED",127 "status": "DEPRECATED",
57 "updated": "2010-10-09T11:30:00Z",128 "updated": "2011-01-21T11:33:21Z",
58 "links": [129 "links": [
59 {130 {
60 "rel": "self",131 "rel": "self",
@@ -64,6 +135,183 @@
64 ]135 ]
65 self.assertEqual(versions, expected)136 self.assertEqual(versions, expected)
66137
138 def test_get_version_1_0_detail(self):
139 req = webob.Request.blank('/v1.0/')
140 req.accept = "application/json"
141 res = req.get_response(fakes.wsgi_app())
142 self.assertEqual(res.status_int, 200)
143 self.assertEqual(res.content_type, "application/json")
144 version = json.loads(res.body)
145 expected = {
146 "version": {
147 "id": "v1.0",
148 "status": "DEPRECATED",
149 "updated": "2011-01-21T11:33:21Z",
150 "links": [
151 {
152 "rel": "self",
153 "href": "http://localhost/v1.0/"
154 },
155 {
156 "rel": "describedby",
157 "type": "application/pdf",
158 "href": "http://docs.rackspacecloud.com/"
159 "servers/api/v1.0/cs-devguide-20110125.pdf"
160 },
161 {
162 "rel": "describedby",
163 "type": "application/vnd.sun.wadl+xml",
164 "href": "http://docs.rackspacecloud.com/"
165 "servers/api/v1.0/application.wadl"
166 }
167 ],
168 "media-types": [
169 {
170 "base": "application/xml",
171 "type": "application/"
172 "vnd.openstack.compute-v1.0+xml"
173 },
174 {
175 "base": "application/json",
176 "type": "application/"
177 "vnd.openstack.compute-v1.0+json"
178 }
179 ]
180 }
181 }
182 self.assertEqual(expected, version)
183
184 def test_get_version_1_1_detail(self):
185 req = webob.Request.blank('/v1.1/')
186 req.accept = "application/json"
187 res = req.get_response(fakes.wsgi_app())
188 self.assertEqual(res.status_int, 200)
189 self.assertEqual(res.content_type, "application/json")
190 version = json.loads(res.body)
191 expected = {
192 "version": {
193 "id": "v1.1",
194 "status": "CURRENT",
195 "updated": "2011-01-21T11:33:21Z",
196 "links": [
197 {
198 "rel": "self",
199 "href": "http://localhost/v1.1/"
200 },
201 {
202 "rel": "describedby",
203 "type": "application/pdf",
204 "href": "http://docs.rackspacecloud.com/"
205 "servers/api/v1.1/cs-devguide-20110125.pdf"
206 },
207 {
208 "rel": "describedby",
209 "type": "application/vnd.sun.wadl+xml",
210 "href": "http://docs.rackspacecloud.com/"
211 "servers/api/v1.1/application.wadl"
212 }
213 ],
214 "media-types": [
215 {
216 "base": "application/xml",
217 "type": "application/"
218 "vnd.openstack.compute-v1.1+xml"
219 },
220 {
221 "base": "application/json",
222 "type": "application/"
223 "vnd.openstack.compute-v1.1+json"
224 }
225 ]
226 }
227 }
228 self.assertEqual(expected, version)
229
230 def test_get_version_1_0_detail_xml(self):
231 req = webob.Request.blank('/v1.0/')
232 req.accept = "application/xml"
233 res = req.get_response(fakes.wsgi_app())
234 self.assertEqual(res.status_int, 200)
235 self.assertEqual(res.content_type, "application/xml")
236 root = xml.etree.ElementTree.XML(res.body)
237 self.assertEqual(root.tag.split('}')[1], "version")
238 self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
239
240 children = list(root)
241 media_types = children[0]
242 media_type_nodes = list(media_types)
243 links = (children[1], children[2], children[3])
244
245 self.assertEqual(media_types.tag.split('}')[1], 'media-types')
246 for media_node in media_type_nodes:
247 self.assertEqual(media_node.tag.split('}')[1], 'media-type')
248
249 expected = """
250 <version id="v1.0" status="DEPRECATED"
251 updated="2011-01-21T11:33:21Z"
252 xmlns="%s"
253 xmlns:atom="http://www.w3.org/2005/Atom">
254
255 <media-types>
256 <media-type base="application/xml"
257 type="application/vnd.openstack.compute-v1.0+xml"/>
258 <media-type base="application/json"
259 type="application/vnd.openstack.compute-v1.0+json"/>
260 </media-types>
261
262 <atom:link href="http://localhost/v1.0/"
263 rel="self"/>
264
265 <atom:link href="http://docs.rackspacecloud.com/servers/
266 api/v1.0/cs-devguide-20110125.pdf"
267 rel="describedby"
268 type="application/pdf"/>
269
270 <atom:link href="http://docs.rackspacecloud.com/servers/
271 api/v1.0/application.wadl"
272 rel="describedby"
273 type="application/vnd.sun.wadl+xml"/>
274 </version>""".replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11
275
276 actual = res.body.replace(" ", "").replace("\n", "")
277 self.assertEqual(expected, actual)
278
279 def test_get_version_1_1_detail_xml(self):
280 req = webob.Request.blank('/v1.1/')
281 req.accept = "application/xml"
282 res = req.get_response(fakes.wsgi_app())
283 self.assertEqual(res.status_int, 200)
284 self.assertEqual(res.content_type, "application/xml")
285 expected = """
286 <version id="v1.1" status="CURRENT"
287 updated="2011-01-21T11:33:21Z"
288 xmlns="%s"
289 xmlns:atom="http://www.w3.org/2005/Atom">
290
291 <media-types>
292 <media-type base="application/xml"
293 type="application/vnd.openstack.compute-v1.1+xml"/>
294 <media-type base="application/json"
295 type="application/vnd.openstack.compute-v1.1+json"/>
296 </media-types>
297
298 <atom:link href="http://localhost/v1.1/"
299 rel="self"/>
300
301 <atom:link href="http://docs.rackspacecloud.com/servers/
302 api/v1.1/cs-devguide-20110125.pdf"
303 rel="describedby"
304 type="application/pdf"/>
305
306 <atom:link href="http://docs.rackspacecloud.com/servers/
307 api/v1.1/application.wadl"
308 rel="describedby"
309 type="application/vnd.sun.wadl+xml"/>
310 </version>""".replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11
311
312 actual = res.body.replace(" ", "").replace("\n", "")
313 self.assertEqual(expected, actual)
314
67 def test_get_version_list_xml(self):315 def test_get_version_list_xml(self):
68 req = webob.Request.blank('/')316 req = webob.Request.blank('/')
69 req.accept = "application/xml"317 req.accept = "application/xml"
@@ -71,18 +319,94 @@
71 self.assertEqual(res.status_int, 200)319 self.assertEqual(res.status_int, 200)
72 self.assertEqual(res.content_type, "application/xml")320 self.assertEqual(res.content_type, "application/xml")
73321
74 expected = """<versions>322 expected = """
75 <version id="v1.1" status="CURRENT" updated="2011-07-18T11:30:00Z">323 <versions xmlns="%s" xmlns:atom="%s">
324 <version id="v1.1" status="CURRENT" updated="2011-01-21T11:33:21Z">
76 <atom:link href="http://localhost/v1.1/" rel="self"/>325 <atom:link href="http://localhost/v1.1/" rel="self"/>
77 </version>326 </version>
78 <version id="v1.0" status="DEPRECATED"327 <version id="v1.0" status="DEPRECATED"
79 updated="2010-10-09T11:30:00Z">328 updated="2011-01-21T11:33:21Z">
80 <atom:link href="http://localhost/v1.0/" rel="self"/>329 <atom:link href="http://localhost/v1.0/" rel="self"/>
81 </version>330 </version>
82 </versions>""".replace(" ", "").replace("\n", "")331 </versions>""".replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11,
83332 wsgi.XMLNS_ATOM)
84 actual = res.body.replace(" ", "").replace("\n", "")333
85334 actual = res.body.replace(" ", "").replace("\n", "")
335
336 self.assertEqual(expected, actual)
337
338 def test_get_version_1_0_detail_atom(self):
339 req = webob.Request.blank('/v1.0/')
340 req.accept = "application/atom+xml"
341 res = req.get_response(fakes.wsgi_app())
342 self.assertEqual(res.status_int, 200)
343 self.assertEqual("application/atom+xml", res.content_type)
344 expected = """
345 <feed xmlns="http://www.w3.org/2005/Atom">
346 <title type="text">About This Version</title>
347 <updated>2011-01-21T11:33:21Z</updated>
348 <id>http://localhost/v1.0/</id>
349 <author>
350 <name>Rackspace</name>
351 <uri>http://www.rackspace.com/</uri>
352 </author>
353 <link href="http://localhost/v1.0/" rel="self"/>
354 <entry>
355 <id>http://localhost/v1.0/</id>
356 <title type="text">Version v1.0</title>
357 <updated>2011-01-21T11:33:21Z</updated>
358 <link href="http://localhost/v1.0/"
359 rel="self"/>
360 <link href="http://docs.rackspacecloud.com/servers/
361 api/v1.0/cs-devguide-20110125.pdf"
362 rel="describedby" type="application/pdf"/>
363 <link href="http://docs.rackspacecloud.com/servers/
364 api/v1.0/application.wadl"
365 rel="describedby" type="application/vnd.sun.wadl+xml"/>
366 <content type="text">
367 Version v1.0 DEPRECATED (2011-01-21T11:33:21Z)
368 </content>
369 </entry>
370 </feed>""".replace(" ", "").replace("\n", "")
371
372 actual = res.body.replace(" ", "").replace("\n", "")
373 self.assertEqual(expected, actual)
374
375 def test_get_version_1_1_detail_atom(self):
376 req = webob.Request.blank('/v1.1/')
377 req.accept = "application/atom+xml"
378 res = req.get_response(fakes.wsgi_app())
379 self.assertEqual(res.status_int, 200)
380 self.assertEqual("application/atom+xml", res.content_type)
381 expected = """
382 <feed xmlns="http://www.w3.org/2005/Atom">
383 <title type="text">About This Version</title>
384 <updated>2011-01-21T11:33:21Z</updated>
385 <id>http://localhost/v1.1/</id>
386 <author>
387 <name>Rackspace</name>
388 <uri>http://www.rackspace.com/</uri>
389 </author>
390 <link href="http://localhost/v1.1/" rel="self"/>
391 <entry>
392 <id>http://localhost/v1.1/</id>
393 <title type="text">Version v1.1</title>
394 <updated>2011-01-21T11:33:21Z</updated>
395 <link href="http://localhost/v1.1/"
396 rel="self"/>
397 <link href="http://docs.rackspacecloud.com/servers/
398 api/v1.1/cs-devguide-20110125.pdf"
399 rel="describedby" type="application/pdf"/>
400 <link href="http://docs.rackspacecloud.com/servers/
401 api/v1.1/application.wadl"
402 rel="describedby" type="application/vnd.sun.wadl+xml"/>
403 <content type="text">
404 Version v1.1 CURRENT (2011-01-21T11:33:21Z)
405 </content>
406 </entry>
407 </feed>""".replace(" ", "").replace("\n", "")
408
409 actual = res.body.replace(" ", "").replace("\n", "")
86 self.assertEqual(expected, actual)410 self.assertEqual(expected, actual)
87411
88 def test_get_version_list_atom(self):412 def test_get_version_list_atom(self):
@@ -95,7 +419,7 @@
95 expected = """419 expected = """
96 <feed xmlns="http://www.w3.org/2005/Atom">420 <feed xmlns="http://www.w3.org/2005/Atom">
97 <title type="text">Available API Versions</title>421 <title type="text">Available API Versions</title>
98 <updated>2011-07-18T11:30:00Z</updated>422 <updated>2011-01-21T11:33:21Z</updated>
99 <id>http://localhost/</id>423 <id>http://localhost/</id>
100 <author>424 <author>
101 <name>Rackspace</name>425 <name>Rackspace</name>
@@ -105,19 +429,19 @@
105 <entry>429 <entry>
106 <id>http://localhost/v1.1/</id>430 <id>http://localhost/v1.1/</id>
107 <title type="text">Version v1.1</title>431 <title type="text">Version v1.1</title>
108 <updated>2011-07-18T11:30:00Z</updated>432 <updated>2011-01-21T11:33:21Z</updated>
109 <link href="http://localhost/v1.1/" rel="self"/>433 <link href="http://localhost/v1.1/" rel="self"/>
110 <content type="text">434 <content type="text">
111 Version v1.1 CURRENT (2011-07-18T11:30:00Z)435 Version v1.1 CURRENT (2011-01-21T11:33:21Z)
112 </content>436 </content>
113 </entry>437 </entry>
114 <entry>438 <entry>
115 <id>http://localhost/v1.0/</id>439 <id>http://localhost/v1.0/</id>
116 <title type="text">Version v1.0</title>440 <title type="text">Version v1.0</title>
117 <updated>2010-10-09T11:30:00Z</updated>441 <updated>2011-01-21T11:33:21Z</updated>
118 <link href="http://localhost/v1.0/" rel="self"/>442 <link href="http://localhost/v1.0/" rel="self"/>
119 <content type="text">443 <content type="text">
120 Version v1.0 DEPRECATED (2010-10-09T11:30:00Z)444 Version v1.0 DEPRECATED (2011-01-21T11:33:21Z)
121 </content>445 </content>
122 </entry>446 </entry>
123 </feed>447 </feed>
@@ -127,28 +451,184 @@
127451
128 self.assertEqual(expected, actual)452 self.assertEqual(expected, actual)
129453
454 def test_multi_choice_image(self):
455 req = webob.Request.blank('/images/1')
456 req.accept = "application/json"
457 res = req.get_response(fakes.wsgi_app())
458 self.assertEqual(res.status_int, 300)
459 self.assertEqual(res.content_type, "application/json")
460
461 expected = {
462 "choices": [
463 {
464 "id": "v1.1",
465 "status": "CURRENT",
466 "links": [
467 {
468 "href": "http://localhost/v1.1/images/1",
469 "rel": "self",
470 },
471 ],
472 "media-types": [
473 {
474 "base": "application/xml",
475 "type": "application/vnd.openstack.compute-v1.1+xml"
476 },
477 {
478 "base": "application/json",
479 "type": "application/vnd.openstack.compute-v1.1+json"
480 },
481 ],
482 },
483 {
484 "id": "v1.0",
485 "status": "DEPRECATED",
486 "links": [
487 {
488 "href": "http://localhost/v1.0/images/1",
489 "rel": "self",
490 },
491 ],
492 "media-types": [
493 {
494 "base": "application/xml",
495 "type": "application/vnd.openstack.compute-v1.0+xml"
496 },
497 {
498 "base": "application/json",
499 "type": "application/vnd.openstack.compute-v1.0+json"
500 },
501 ],
502 },
503 ], }
504
505 self.assertDictMatch(expected, json.loads(res.body))
506
507 def test_multi_choice_image_xml(self):
508 req = webob.Request.blank('/images/1')
509 req.accept = "application/xml"
510 res = req.get_response(fakes.wsgi_app())
511 self.assertEqual(res.status_int, 300)
512 self.assertEqual(res.content_type, "application/xml")
513
514 expected = """
515 <choices xmlns="%s" xmlns:atom="%s">
516 <version id="v1.1" status="CURRENT">
517 <media-types>
518 <media-type base="application/xml"
519 type="application/vnd.openstack.compute-v1.1+xml"/>
520 <media-type base="application/json"
521 type="application/vnd.openstack.compute-v1.1+json"/>
522 </media-types>
523 <atom:link href="http://localhost/v1.1/images/1" rel="self"/>
524 </version>
525 <version id="v1.0" status="DEPRECATED">
526 <media-types>
527 <media-type base="application/xml"
528 type="application/vnd.openstack.compute-v1.0+xml"/>
529 <media-type base="application/json"
530 type="application/vnd.openstack.compute-v1.0+json"/>
531 </media-types>
532 <atom:link href="http://localhost/v1.0/images/1" rel="self"/>
533 </version>
534 </choices>""".replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11,
535 wsgi.XMLNS_ATOM)
536
537 def test_multi_choice_server_atom(self):
538 """
539 Make sure multi choice responses do not have content-type
540 application/atom+xml (should use default of json)
541 """
542 req = webob.Request.blank('/servers/2')
543 req.accept = "application/atom+xml"
544 res = req.get_response(fakes.wsgi_app())
545 self.assertEqual(res.status_int, 300)
546 self.assertEqual(res.content_type, "application/json")
547
548 def test_multi_choice_server(self):
549 req = webob.Request.blank('/servers/2')
550 req.accept = "application/json"
551 res = req.get_response(fakes.wsgi_app())
552 self.assertEqual(res.status_int, 300)
553 self.assertEqual(res.content_type, "application/json")
554
555 expected = {
556 "choices": [
557 {
558 "id": "v1.1",
559 "status": "CURRENT",
560 "links": [
561 {
562 "href": "http://localhost/v1.1/servers/2",
563 "rel": "self",
564 },
565 ],
566 "media-types": [
567 {
568 "base": "application/xml",
569 "type": "application/vnd.openstack.compute-v1.1+xml"
570 },
571 {
572 "base": "application/json",
573 "type": "application/vnd.openstack.compute-v1.1+json"
574 },
575 ],
576 },
577 {
578 "id": "v1.0",
579 "status": "DEPRECATED",
580 "links": [
581 {
582 "href": "http://localhost/v1.0/servers/2",
583 "rel": "self",
584 },
585 ],
586 "media-types": [
587 {
588 "base": "application/xml",
589 "type": "application/vnd.openstack.compute-v1.0+xml"
590 },
591 {
592 "base": "application/json",
593 "type": "application/vnd.openstack.compute-v1.0+json"
594 },
595 ],
596 },
597 ], }
598
599 self.assertDictMatch(expected, json.loads(res.body))
600
601
602class VersionsViewBuilderTests(test.TestCase):
130 def test_view_builder(self):603 def test_view_builder(self):
131 base_url = "http://example.org/"604 base_url = "http://example.org/"
132605
133 version_data = {606 version_data = {
134 "id": "3.2.1",607 "v3.2.1": {
135 "status": "CURRENT",608 "id": "3.2.1",
136 "updated": "2011-07-18T11:30:00Z"}609 "status": "CURRENT",
610 "updated": "2011-07-18T11:30:00Z",
611 }
612 }
137613
138 expected = {614 expected = {
139 "id": "3.2.1",615 "versions": [
140 "status": "CURRENT",
141 "updated": "2011-07-18T11:30:00Z",
142 "links": [
143 {616 {
144 "rel": "self",617 "id": "3.2.1",
145 "href": "http://example.org/3.2.1/",618 "status": "CURRENT",
146 },619 "updated": "2011-07-18T11:30:00Z",
147 ],620 "links": [
621 {
622 "rel": "self",
623 "href": "http://example.org/3.2.1/",
624 },
625 ],
626 }
627 ]
148 }628 }
149629
150 builder = views.versions.ViewBuilder(base_url)630 builder = views.versions.ViewBuilder(base_url)
151 output = builder.build(version_data)631 output = builder.build_versions(version_data)
152632
153 self.assertEqual(output, expected)633 self.assertEqual(output, expected)
154634
@@ -163,7 +643,9 @@
163643
164 self.assertEqual(actual, expected)644 self.assertEqual(actual, expected)
165645
166 def test_xml_serializer(self):646
647class VersionsSerializerTests(test.TestCase):
648 def test_versions_list_xml_serializer(self):
167 versions_data = {649 versions_data = {
168 'versions': [650 'versions': [
169 {651 {
@@ -180,20 +662,137 @@
180 ]662 ]
181 }663 }
182664
183 expected = """665 serializer = versions.VersionsXMLSerializer()
184 <versions>666 response = serializer.index(versions_data)
185 <version id="2.7.1" status="DEPRECATED"667
186 updated="2011-07-18T11:30:00Z">668 root = xml.etree.ElementTree.XML(response)
187 <atom:link href="http://test/2.7.1" rel="self"/>669 self.assertEqual(root.tag.split('}')[1], "versions")
188 </version>670 self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
189 </versions>""".replace(" ", "").replace("\n", "")671 version = list(root)[0]
190672 self.assertEqual(version.tag.split('}')[1], "version")
191 serializer = versions.VersionsXMLSerializer()673 self.assertEqual(version.get('id'),
192 response = serializer.default(versions_data)674 versions_data['versions'][0]['id'])
193 response = response.replace(" ", "").replace("\n", "")675 self.assertEqual(version.get('status'),
194 self.assertEqual(expected, response)676 versions_data['versions'][0]['status'])
195677
196 def test_atom_serializer(self):678 link = list(version)[0]
679
680 self.assertEqual(link.tag.split('}')[1], "link")
681 self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM)
682 for key, val in versions_data['versions'][0]['links'][0].items():
683 self.assertEqual(link.get(key), val)
684
685 def test_versions_multi_xml_serializer(self):
686 versions_data = {
687 'choices': [
688 {
689 "id": "2.7.1",
690 "updated": "2011-07-18T11:30:00Z",
691 "status": "DEPRECATED",
692 "media-types": VERSIONS['v1.1']['media-types'],
693 "links": [
694 {
695 "rel": "self",
696 "href": "http://test/2.7.1/images",
697 },
698 ],
699 },
700 ]
701 }
702
703 serializer = versions.VersionsXMLSerializer()
704 response = serializer.multi(versions_data)
705
706 root = xml.etree.ElementTree.XML(response)
707 self.assertEqual(root.tag.split('}')[1], "choices")
708 self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
709 version = list(root)[0]
710 self.assertEqual(version.tag.split('}')[1], "version")
711 self.assertEqual(version.get('id'), versions_data['choices'][0]['id'])
712 self.assertEqual(version.get('status'),
713 versions_data['choices'][0]['status'])
714
715 media_types = list(version)[0]
716 media_type_nodes = list(media_types)
717 self.assertEqual(media_types.tag.split('}')[1], "media-types")
718
719 set_types = versions_data['choices'][0]['media-types']
720 for i, type in enumerate(set_types):
721 node = media_type_nodes[i]
722 self.assertEqual(node.tag.split('}')[1], "media-type")
723 for key, val in set_types[i].items():
724 self.assertEqual(node.get(key), val)
725
726 link = list(version)[1]
727
728 self.assertEqual(link.tag.split('}')[1], "link")
729 self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM)
730 for key, val in versions_data['choices'][0]['links'][0].items():
731 self.assertEqual(link.get(key), val)
732
733 def test_version_detail_xml_serializer(self):
734 version_data = {
735 "version": {
736 "id": "v1.0",
737 "status": "CURRENT",
738 "updated": "2011-01-21T11:33:21Z",
739 "links": [
740 {
741 "rel": "self",
742 "href": "http://localhost/v1.0/"
743 },
744 {
745 "rel": "describedby",
746 "type": "application/pdf",
747 "href": "http://docs.rackspacecloud.com/"
748 "servers/api/v1.0/cs-devguide-20110125.pdf"
749 },
750 {
751 "rel": "describedby",
752 "type": "application/vnd.sun.wadl+xml",
753 "href": "http://docs.rackspacecloud.com/"
754 "servers/api/v1.0/application.wadl"
755 },
756 ],
757 "media-types": [
758 {
759 "base": "application/xml",
760 "type": "application/vnd.openstack.compute-v1.0+xml"
761 },
762 {
763 "base": "application/json",
764 "type": "application/vnd.openstack.compute-v1.0+json"
765 }
766 ],
767 },
768 }
769
770 serializer = versions.VersionsXMLSerializer()
771 response = serializer.show(version_data)
772
773 root = xml.etree.ElementTree.XML(response)
774 self.assertEqual(root.tag.split('}')[1], "version")
775 self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11)
776
777 children = list(root)
778 media_types = children[0]
779 media_type_nodes = list(media_types)
780 links = (children[1], children[2], children[3])
781
782 self.assertEqual(media_types.tag.split('}')[1], 'media-types')
783 for i, media_node in enumerate(media_type_nodes):
784 self.assertEqual(media_node.tag.split('}')[1], 'media-type')
785 for key, val in version_data['version']['media-types'][i].items():
786 self.assertEqual(val, media_node.get(key))
787
788 for i, link in enumerate(links):
789 self.assertEqual(link.tag.split('}')[0].strip('{'),
790 'http://www.w3.org/2005/Atom')
791 self.assertEqual(link.tag.split('}')[1], 'link')
792 for key, val in version_data['version']['links'][i].items():
793 self.assertEqual(val, link.get(key))
794
795 def test_versions_list_atom_serializer(self):
197 versions_data = {796 versions_data = {
198 'versions': [797 'versions': [
199 {798 {
@@ -210,45 +809,158 @@
210 ]809 ]
211 }810 }
212811
213 expected = """812 serializer = versions.VersionsAtomSerializer()
214 <feed xmlns="http://www.w3.org/2005/Atom">813 response = serializer.index(versions_data)
215 <title type="text">814
216 Available API Versions815 root = xml.etree.ElementTree.XML(response)
217 </title>816 self.assertEqual(root.tag.split('}')[1], "feed")
218 <updated>817 self.assertEqual(root.tag.split('}')[0].strip('{'),
219 2011-07-20T11:40:00Z818 "http://www.w3.org/2005/Atom")
220 </updated>819
221 <id>820 children = list(root)
222 http://test/821 title = children[0]
223 </id>822 updated = children[1]
224 <author>823 id = children[2]
225 <name>824 author = children[3]
226 Rackspace825 link = children[4]
227 </name>826 entry = children[5]
228 <uri>827
229 http://www.rackspace.com/828 self.assertEqual(title.tag.split('}')[1], 'title')
230 </uri>829 self.assertEqual(title.text, 'Available API Versions')
231 </author>830 self.assertEqual(updated.tag.split('}')[1], 'updated')
232 <link href="http://test/" rel="self"/>831 self.assertEqual(updated.text, '2011-07-20T11:40:00Z')
233 <entry>832 self.assertEqual(id.tag.split('}')[1], 'id')
234 <id>833 self.assertEqual(id.text, 'http://test/')
235 http://test/2.9.8834
236 </id>835 self.assertEqual(author.tag.split('}')[1], 'author')
237 <title type="text">836 author_name = list(author)[0]
238 Version 2.9.8837 author_uri = list(author)[1]
239 </title>838 self.assertEqual(author_name.tag.split('}')[1], 'name')
240 <updated>839 self.assertEqual(author_name.text, 'Rackspace')
241 2011-07-20T11:40:00Z840 self.assertEqual(author_uri.tag.split('}')[1], 'uri')
242 </updated>841 self.assertEqual(author_uri.text, 'http://www.rackspace.com/')
243 <link href="http://test/2.9.8" rel="self"/>842
244 <content type="text">843 self.assertEqual(link.get('href'), 'http://test/')
245 Version 2.9.8 CURRENT (2011-07-20T11:40:00Z)844 self.assertEqual(link.get('rel'), 'self')
246 </content>845
247 </entry>846 self.assertEqual(entry.tag.split('}')[1], 'entry')
248 </feed>""".replace(" ", "").replace("\n", "")847 entry_children = list(entry)
249848 entry_id = entry_children[0]
250 serializer = versions.VersionsAtomSerializer()849 entry_title = entry_children[1]
251 response = serializer.default(versions_data)850 entry_updated = entry_children[2]
252 print response851 entry_link = entry_children[3]
253 response = response.replace(" ", "").replace("\n", "")852 entry_content = entry_children[4]
254 self.assertEqual(expected, response)853 self.assertEqual(entry_id.tag.split('}')[1], "id")
854 self.assertEqual(entry_id.text, "http://test/2.9.8")
855 self.assertEqual(entry_title.tag.split('}')[1], "title")
856 self.assertEqual(entry_title.get('type'), "text")
857 self.assertEqual(entry_title.text, "Version 2.9.8")
858 self.assertEqual(entry_updated.tag.split('}')[1], "updated")
859 self.assertEqual(entry_updated.text, "2011-07-20T11:40:00Z")
860 self.assertEqual(entry_link.tag.split('}')[1], "link")
861 self.assertEqual(entry_link.get('href'), "http://test/2.9.8")
862 self.assertEqual(entry_link.get('rel'), "self")
863 self.assertEqual(entry_content.tag.split('}')[1], "content")
864 self.assertEqual(entry_content.get('type'), "text")
865 self.assertEqual(entry_content.text,
866 "Version 2.9.8 CURRENT (2011-07-20T11:40:00Z)")
867
868 def test_version_detail_atom_serializer(self):
869 versions_data = {
870 "version": {
871 "id": "v1.1",
872 "status": "CURRENT",
873 "updated": "2011-01-21T11:33:21Z",
874 "links": [
875 {
876 "rel": "self",
877 "href": "http://localhost/v1.1/"
878 },
879 {
880 "rel": "describedby",
881 "type": "application/pdf",
882 "href": "http://docs.rackspacecloud.com/"
883 "servers/api/v1.1/cs-devguide-20110125.pdf"
884 },
885 {
886 "rel": "describedby",
887 "type": "application/vnd.sun.wadl+xml",
888 "href": "http://docs.rackspacecloud.com/"
889 "servers/api/v1.1/application.wadl"
890 },
891 ],
892 "media-types": [
893 {
894 "base": "application/xml",
895 "type": "application/vnd.openstack.compute-v1.1+xml"
896 },
897 {
898 "base": "application/json",
899 "type": "application/vnd.openstack.compute-v1.1+json"
900 }
901 ],
902 },
903 }
904
905 serializer = versions.VersionsAtomSerializer()
906 response = serializer.show(versions_data)
907
908 root = xml.etree.ElementTree.XML(response)
909 self.assertEqual(root.tag.split('}')[1], "feed")
910 self.assertEqual(root.tag.split('}')[0].strip('{'),
911 "http://www.w3.org/2005/Atom")
912
913 children = list(root)
914 title = children[0]
915 updated = children[1]
916 id = children[2]
917 author = children[3]
918 link = children[4]
919 entry = children[5]
920
921 self.assertEqual(root.tag.split('}')[1], 'feed')
922 self.assertEqual(title.tag.split('}')[1], 'title')
923 self.assertEqual(title.text, 'About This Version')
924 self.assertEqual(updated.tag.split('}')[1], 'updated')
925 self.assertEqual(updated.text, '2011-01-21T11:33:21Z')
926 self.assertEqual(id.tag.split('}')[1], 'id')
927 self.assertEqual(id.text, 'http://localhost/v1.1/')
928
929 self.assertEqual(author.tag.split('}')[1], 'author')
930 author_name = list(author)[0]
931 author_uri = list(author)[1]
932 self.assertEqual(author_name.tag.split('}')[1], 'name')
933 self.assertEqual(author_name.text, 'Rackspace')
934 self.assertEqual(author_uri.tag.split('}')[1], 'uri')
935 self.assertEqual(author_uri.text, 'http://www.rackspace.com/')
936
937 self.assertEqual(link.get('href'),
938 'http://localhost/v1.1/')
939 self.assertEqual(link.get('rel'), 'self')
940
941 self.assertEqual(entry.tag.split('}')[1], 'entry')
942 entry_children = list(entry)
943 entry_id = entry_children[0]
944 entry_title = entry_children[1]
945 entry_updated = entry_children[2]
946 entry_links = (entry_children[3], entry_children[4], entry_children[5])
947 entry_content = entry_children[6]
948
949 self.assertEqual(entry_id.tag.split('}')[1], "id")
950 self.assertEqual(entry_id.text,
951 "http://localhost/v1.1/")
952 self.assertEqual(entry_title.tag.split('}')[1], "title")
953 self.assertEqual(entry_title.get('type'), "text")
954 self.assertEqual(entry_title.text, "Version v1.1")
955 self.assertEqual(entry_updated.tag.split('}')[1], "updated")
956 self.assertEqual(entry_updated.text, "2011-01-21T11:33:21Z")
957
958 for i, link in enumerate(versions_data["version"]["links"]):
959 self.assertEqual(entry_links[i].tag.split('}')[1], "link")
960 for key, val in versions_data["version"]["links"][i].items():
961 self.assertEqual(entry_links[i].get(key), val)
962
963 self.assertEqual(entry_content.tag.split('}')[1], "content")
964 self.assertEqual(entry_content.get('type'), "text")
965 self.assertEqual(entry_content.text,
966 "Version v1.1 CURRENT (2011-01-21T11:33:21Z)")