Merge lp:~rackspace-titan/nova/versions_detailed_json_xml into lp:~hudson-openstack/nova/trunk
- versions_detailed_json_xml
- Merge into trunk
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 | ||||||||
Related bugs: |
|
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 |
Commit message
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.
- 1327. By William Wolf
-
merge from trunk
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)
William Wolf (throughnothing) wrote : | # |
Alright, I think I got the wsgi xmlns variables in use, and fixed up the self link issues.
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://
52 + "servers/
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.
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
Brian Waldon (bcwaldon) wrote : | # |
Leave Gabe to me.
- 1335. By William Wolf
-
merge from trunk
- 1336. By William Wolf
-
merge from trunk
Alex Meade (alex-meade) wrote : | # |
Good work nailing this branch down
- 1337. By William Wolf
-
merge from trunk
- 1338. By William Wolf
-
merge from trunk
Preview Diff
1 | === modified file 'nova/api/openstack/__init__.py' |
2 | --- nova/api/openstack/__init__.py 2011-07-22 21:04:26 +0000 |
3 | +++ nova/api/openstack/__init__.py 2011-08-02 20:52:09 +0000 |
4 | @@ -40,6 +40,7 @@ |
5 | from nova.api.openstack import server_metadata |
6 | from nova.api.openstack import shared_ip_groups |
7 | from nova.api.openstack import users |
8 | +from nova.api.openstack import versions |
9 | from nova.api.openstack import wsgi |
10 | from nova.api.openstack import zones |
11 | |
12 | @@ -115,6 +116,10 @@ |
13 | 'select': 'POST', |
14 | 'boot': 'POST'}) |
15 | |
16 | + mapper.connect("versions", "/", |
17 | + controller=versions.create_resource(version), |
18 | + action='show') |
19 | + |
20 | mapper.resource("console", "consoles", |
21 | controller=consoles.create_resource(), |
22 | parent_resource=dict(member_name='server', |
23 | |
24 | === modified file 'nova/api/openstack/versions.py' |
25 | --- nova/api/openstack/versions.py 2011-07-21 16:19:09 +0000 |
26 | +++ nova/api/openstack/versions.py 2011-08-02 20:52:09 +0000 |
27 | @@ -24,7 +24,66 @@ |
28 | from nova.api.openstack import wsgi |
29 | |
30 | |
31 | -ATOM_XMLNS = "http://www.w3.org/2005/Atom" |
32 | +VERSIONS = { |
33 | + "v1.0": { |
34 | + "id": "v1.0", |
35 | + "status": "DEPRECATED", |
36 | + "updated": "2011-01-21T11:33:21Z", |
37 | + "links": [ |
38 | + { |
39 | + "rel": "describedby", |
40 | + "type": "application/pdf", |
41 | + "href": "http://docs.rackspacecloud.com/" |
42 | + "servers/api/v1.0/cs-devguide-20110125.pdf" |
43 | + }, |
44 | + { |
45 | + "rel": "describedby", |
46 | + "type": "application/vnd.sun.wadl+xml", |
47 | + "href": "http://docs.rackspacecloud.com/" |
48 | + "servers/api/v1.0/application.wadl" |
49 | + }, |
50 | + ], |
51 | + "media-types": [ |
52 | + { |
53 | + "base": "application/xml", |
54 | + "type": "application/vnd.openstack.compute-v1.0+xml" |
55 | + }, |
56 | + { |
57 | + "base": "application/json", |
58 | + "type": "application/vnd.openstack.compute-v1.0+json" |
59 | + } |
60 | + ], |
61 | + }, |
62 | + "v1.1": { |
63 | + "id": "v1.1", |
64 | + "status": "CURRENT", |
65 | + "updated": "2011-01-21T11:33:21Z", |
66 | + "links": [ |
67 | + { |
68 | + "rel": "describedby", |
69 | + "type": "application/pdf", |
70 | + "href": "http://docs.rackspacecloud.com/" |
71 | + "servers/api/v1.1/cs-devguide-20110125.pdf" |
72 | + }, |
73 | + { |
74 | + "rel": "describedby", |
75 | + "type": "application/vnd.sun.wadl+xml", |
76 | + "href": "http://docs.rackspacecloud.com/" |
77 | + "servers/api/v1.1/application.wadl" |
78 | + }, |
79 | + ], |
80 | + "media-types": [ |
81 | + { |
82 | + "base": "application/xml", |
83 | + "type": "application/vnd.openstack.compute-v1.1+xml" |
84 | + }, |
85 | + { |
86 | + "base": "application/json", |
87 | + "type": "application/vnd.openstack.compute-v1.1+json" |
88 | + } |
89 | + ], |
90 | + }, |
91 | +} |
92 | |
93 | |
94 | class Versions(wsgi.Resource): |
95 | @@ -36,16 +95,20 @@ |
96 | } |
97 | } |
98 | |
99 | + headers_serializer = VersionsHeadersSerializer() |
100 | + |
101 | body_serializers = { |
102 | 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), |
103 | 'application/xml': VersionsXMLSerializer(metadata=metadata), |
104 | } |
105 | - serializer = wsgi.ResponseSerializer(body_serializers) |
106 | + serializer = wsgi.ResponseSerializer( |
107 | + body_serializers=body_serializers, |
108 | + headers_serializer=headers_serializer) |
109 | |
110 | supported_content_types = ('application/json', |
111 | 'application/xml', |
112 | 'application/atom+xml') |
113 | - deserializer = wsgi.RequestDeserializer( |
114 | + deserializer = VersionsRequestDeserializer( |
115 | supported_content_types=supported_content_types) |
116 | |
117 | wsgi.Resource.__init__(self, None, serializer=serializer, |
118 | @@ -53,60 +116,131 @@ |
119 | |
120 | def dispatch(self, request, *args): |
121 | """Respond to a request for all OpenStack API versions.""" |
122 | - version_objs = [ |
123 | - { |
124 | - "id": "v1.1", |
125 | - "status": "CURRENT", |
126 | - #TODO(wwolf) get correct value for these |
127 | - "updated": "2011-07-18T11:30:00Z", |
128 | - }, |
129 | - { |
130 | - "id": "v1.0", |
131 | - "status": "DEPRECATED", |
132 | - #TODO(wwolf) get correct value for these |
133 | - "updated": "2010-10-09T11:30:00Z", |
134 | - }, |
135 | - ] |
136 | - |
137 | builder = nova.api.openstack.views.versions.get_view_builder(request) |
138 | - versions = [builder.build(version) for version in version_objs] |
139 | - return dict(versions=versions) |
140 | + if request.path == '/': |
141 | + # List Versions |
142 | + return builder.build_versions(VERSIONS) |
143 | + else: |
144 | + # Versions Multiple Choice |
145 | + return builder.build_choices(VERSIONS, request) |
146 | + |
147 | + |
148 | +class VersionV10(object): |
149 | + def show(self, req): |
150 | + builder = nova.api.openstack.views.versions.get_view_builder(req) |
151 | + return builder.build_version(VERSIONS['v1.0']) |
152 | + |
153 | + |
154 | +class VersionV11(object): |
155 | + def show(self, req): |
156 | + builder = nova.api.openstack.views.versions.get_view_builder(req) |
157 | + return builder.build_version(VERSIONS['v1.1']) |
158 | + |
159 | + |
160 | +class VersionsRequestDeserializer(wsgi.RequestDeserializer): |
161 | + def get_expected_content_type(self, request): |
162 | + supported_content_types = list(self.supported_content_types) |
163 | + if request.path != '/': |
164 | + # Remove atom+xml accept type for 300 responses |
165 | + if 'application/atom+xml' in supported_content_types: |
166 | + supported_content_types.remove('application/atom+xml') |
167 | + |
168 | + return request.best_match_content_type(supported_content_types) |
169 | + |
170 | + def get_action_args(self, request_environment): |
171 | + """Parse dictionary created by routes library.""" |
172 | + args = {} |
173 | + if request_environment['PATH_INFO'] == '/': |
174 | + args['action'] = 'index' |
175 | + else: |
176 | + args['action'] = 'multi' |
177 | + |
178 | + return args |
179 | |
180 | |
181 | class VersionsXMLSerializer(wsgi.XMLDictSerializer): |
182 | - def _versions_to_xml(self, versions): |
183 | - root = self._xml_doc.createElement('versions') |
184 | + #TODO(wwolf): this is temporary until we get rid of toprettyxml |
185 | + # in the base class (XMLDictSerializer), which I plan to do in |
186 | + # another branch |
187 | + def to_xml_string(self, node, has_atom=False): |
188 | + self._add_xmlns(node, has_atom) |
189 | + return node.toxml(encoding='UTF-8') |
190 | + |
191 | + def _versions_to_xml(self, versions, name="versions", xmlns=None): |
192 | + root = self._xml_doc.createElement(name) |
193 | + root.setAttribute("xmlns", wsgi.XMLNS_V11) |
194 | + root.setAttribute("xmlns:atom", wsgi.XMLNS_ATOM) |
195 | |
196 | for version in versions: |
197 | root.appendChild(self._create_version_node(version)) |
198 | |
199 | return root |
200 | |
201 | - def _create_version_node(self, version): |
202 | + def _create_media_types(self, media_types): |
203 | + base = self._xml_doc.createElement('media-types') |
204 | + for type in media_types: |
205 | + node = self._xml_doc.createElement('media-type') |
206 | + node.setAttribute('base', type['base']) |
207 | + node.setAttribute('type', type['type']) |
208 | + base.appendChild(node) |
209 | + |
210 | + return base |
211 | + |
212 | + def _create_version_node(self, version, create_ns=False): |
213 | version_node = self._xml_doc.createElement('version') |
214 | + if create_ns: |
215 | + xmlns = wsgi.XMLNS_V11 |
216 | + xmlns_atom = wsgi.XMLNS_ATOM |
217 | + version_node.setAttribute('xmlns', xmlns) |
218 | + version_node.setAttribute('xmlns:atom', xmlns_atom) |
219 | + |
220 | version_node.setAttribute('id', version['id']) |
221 | version_node.setAttribute('status', version['status']) |
222 | - version_node.setAttribute('updated', version['updated']) |
223 | - |
224 | - for link in version['links']: |
225 | - link_node = self._xml_doc.createElement('atom:link') |
226 | - link_node.setAttribute('rel', link['rel']) |
227 | - link_node.setAttribute('href', link['href']) |
228 | - version_node.appendChild(link_node) |
229 | + if 'updated' in version: |
230 | + version_node.setAttribute('updated', version['updated']) |
231 | + |
232 | + if 'media-types' in version: |
233 | + media_types = self._create_media_types(version['media-types']) |
234 | + version_node.appendChild(media_types) |
235 | + |
236 | + link_nodes = self._create_link_nodes(self._xml_doc, version['links']) |
237 | + for link in link_nodes: |
238 | + version_node.appendChild(link) |
239 | |
240 | return version_node |
241 | |
242 | - def default(self, data): |
243 | + def index(self, data): |
244 | self._xml_doc = minidom.Document() |
245 | node = self._versions_to_xml(data['versions']) |
246 | |
247 | return self.to_xml_string(node) |
248 | |
249 | + def show(self, data): |
250 | + self._xml_doc = minidom.Document() |
251 | + node = self._create_version_node(data['version'], True) |
252 | + |
253 | + return self.to_xml_string(node) |
254 | + |
255 | + def multi(self, data): |
256 | + self._xml_doc = minidom.Document() |
257 | + node = self._versions_to_xml(data['choices'], 'choices', |
258 | + xmlns=wsgi.XMLNS_V11) |
259 | + |
260 | + return self.to_xml_string(node) |
261 | + |
262 | |
263 | class VersionsAtomSerializer(wsgi.XMLDictSerializer): |
264 | + #TODO(wwolf): this is temporary until we get rid of toprettyxml |
265 | + # in the base class (XMLDictSerializer), which I plan to do in |
266 | + # another branch |
267 | + def to_xml_string(self, node, has_atom=False): |
268 | + self._add_xmlns(node, has_atom) |
269 | + return node.toxml(encoding='UTF-8') |
270 | + |
271 | def __init__(self, metadata=None, xmlns=None): |
272 | + self.metadata = metadata or {} |
273 | if not xmlns: |
274 | - self.xmlns = ATOM_XMLNS |
275 | + self.xmlns = wsgi.XMLNS_ATOM |
276 | else: |
277 | self.xmlns = xmlns |
278 | |
279 | @@ -135,8 +269,33 @@ |
280 | link_href = link_href.rstrip('/') |
281 | return link_href.rsplit('/', 1)[0] + '/' |
282 | |
283 | - def _create_meta(self, root, versions): |
284 | - title = self._create_text_elem('title', 'Available API Versions', |
285 | + def _create_detail_meta(self, root, version): |
286 | + title = self._create_text_elem('title', "About This Version", |
287 | + type='text') |
288 | + |
289 | + updated = self._create_text_elem('updated', version['updated']) |
290 | + |
291 | + uri = version['links'][0]['href'] |
292 | + id = self._create_text_elem('id', uri) |
293 | + |
294 | + link = self._xml_doc.createElement('link') |
295 | + link.setAttribute('rel', 'self') |
296 | + link.setAttribute('href', uri) |
297 | + |
298 | + author = self._xml_doc.createElement('author') |
299 | + author_name = self._create_text_elem('name', 'Rackspace') |
300 | + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/') |
301 | + author.appendChild(author_name) |
302 | + author.appendChild(author_uri) |
303 | + |
304 | + root.appendChild(title) |
305 | + root.appendChild(updated) |
306 | + root.appendChild(id) |
307 | + root.appendChild(author) |
308 | + root.appendChild(link) |
309 | + |
310 | + def _create_list_meta(self, root, versions): |
311 | + title = self._create_text_elem('title', "Available API Versions", |
312 | type='text') |
313 | # Set this updated to the most recently updated version |
314 | recent = self._get_most_recent_update(versions) |
315 | @@ -144,6 +303,7 @@ |
316 | |
317 | base_url = self._get_base_url(versions[0]['links'][0]['href']) |
318 | id = self._create_text_elem('id', base_url) |
319 | + |
320 | link = self._xml_doc.createElement('link') |
321 | link.setAttribute('rel', 'self') |
322 | link.setAttribute('href', base_url) |
323 | @@ -178,7 +338,10 @@ |
324 | link_node = self._xml_doc.createElement('link') |
325 | link_node.setAttribute('rel', link['rel']) |
326 | link_node.setAttribute('href', link['href']) |
327 | - entry.appendChild(link_node) |
328 | + if 'type' in link: |
329 | + link_node.setAttribute('type', link['type']) |
330 | + |
331 | + entry.appendChild(link_node) |
332 | |
333 | content = self._create_text_elem('content', |
334 | 'Version %s %s (%s)' % |
335 | @@ -190,10 +353,45 @@ |
336 | entry.appendChild(content) |
337 | root.appendChild(entry) |
338 | |
339 | - def default(self, data): |
340 | + def index(self, data): |
341 | self._xml_doc = minidom.Document() |
342 | node = self._xml_doc.createElementNS(self.xmlns, 'feed') |
343 | - self._create_meta(node, data['versions']) |
344 | + self._create_list_meta(node, data['versions']) |
345 | self._create_version_entries(node, data['versions']) |
346 | |
347 | return self.to_xml_string(node) |
348 | + |
349 | + def show(self, data): |
350 | + self._xml_doc = minidom.Document() |
351 | + node = self._xml_doc.createElementNS(self.xmlns, 'feed') |
352 | + self._create_detail_meta(node, data['version']) |
353 | + self._create_version_entries(node, [data['version']]) |
354 | + |
355 | + return self.to_xml_string(node) |
356 | + |
357 | + |
358 | +class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer): |
359 | + def multi(self, response, data): |
360 | + response.status_int = 300 |
361 | + |
362 | + |
363 | +def create_resource(version='1.0'): |
364 | + controller = { |
365 | + '1.0': VersionV10, |
366 | + '1.1': VersionV11, |
367 | + }[version]() |
368 | + |
369 | + body_serializers = { |
370 | + 'application/xml': VersionsXMLSerializer(), |
371 | + 'application/atom+xml': VersionsAtomSerializer(), |
372 | + } |
373 | + serializer = wsgi.ResponseSerializer(body_serializers) |
374 | + |
375 | + supported_content_types = ('application/json', |
376 | + 'application/xml', |
377 | + 'application/atom+xml') |
378 | + deserializer = wsgi.RequestDeserializer( |
379 | + supported_content_types=supported_content_types) |
380 | + |
381 | + return wsgi.Resource(controller, serializer=serializer, |
382 | + deserializer=deserializer) |
383 | |
384 | === modified file 'nova/api/openstack/views/versions.py' |
385 | --- nova/api/openstack/views/versions.py 2011-07-20 20:47:17 +0000 |
386 | +++ nova/api/openstack/views/versions.py 2011-08-02 20:52:09 +0000 |
387 | @@ -15,6 +15,7 @@ |
388 | # License for the specific language governing permissions and limitations |
389 | # under the License. |
390 | |
391 | +import copy |
392 | import os |
393 | |
394 | |
395 | @@ -31,16 +32,44 @@ |
396 | """ |
397 | self.base_url = base_url |
398 | |
399 | - def build(self, version_data): |
400 | - """Generic method used to generate a version entity.""" |
401 | - version = { |
402 | - "id": version_data["id"], |
403 | - "status": version_data["status"], |
404 | - "updated": version_data["updated"], |
405 | - "links": self._build_links(version_data), |
406 | - } |
407 | - |
408 | - return version |
409 | + def build_choices(self, VERSIONS, req): |
410 | + version_objs = [] |
411 | + for version in VERSIONS: |
412 | + version = VERSIONS[version] |
413 | + version_objs.append({ |
414 | + "id": version['id'], |
415 | + "status": version['status'], |
416 | + "links": [ |
417 | + { |
418 | + "rel": "self", |
419 | + "href": self.generate_href(version['id'], req.path) |
420 | + } |
421 | + ], |
422 | + "media-types": version['media-types'] |
423 | + }) |
424 | + |
425 | + return dict(choices=version_objs) |
426 | + |
427 | + def build_versions(self, versions): |
428 | + version_objs = [] |
429 | + for version in versions: |
430 | + version = versions[version] |
431 | + version_objs.append({ |
432 | + "id": version['id'], |
433 | + "status": version['status'], |
434 | + "updated": version['updated'], |
435 | + "links": self._build_links(version), |
436 | + }) |
437 | + |
438 | + return dict(versions=version_objs) |
439 | + |
440 | + def build_version(self, version): |
441 | + reval = copy.deepcopy(version) |
442 | + reval['links'].insert(0, { |
443 | + "rel": "self", |
444 | + "href": self.base_url.rstrip('/') + '/', |
445 | + }) |
446 | + return dict(version=reval) |
447 | |
448 | def _build_links(self, version_data): |
449 | """Generate a container of links that refer to the provided version.""" |
450 | @@ -55,6 +84,11 @@ |
451 | |
452 | return links |
453 | |
454 | - def generate_href(self, version_number): |
455 | + def generate_href(self, version_number, path=None): |
456 | """Create an url that refers to a specific version_number.""" |
457 | - return os.path.join(self.base_url, version_number) + '/' |
458 | + version_number = version_number.strip('/') |
459 | + if path: |
460 | + path = path.strip('/') |
461 | + return os.path.join(self.base_url, version_number, path) |
462 | + else: |
463 | + return os.path.join(self.base_url, version_number) + '/' |
464 | |
465 | === modified file 'nova/api/openstack/wsgi.py' |
466 | --- nova/api/openstack/wsgi.py 2011-07-28 21:59:25 +0000 |
467 | +++ nova/api/openstack/wsgi.py 2011-08-02 20:52:09 +0000 |
468 | @@ -13,6 +13,7 @@ |
469 | |
470 | XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' |
471 | XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' |
472 | + |
473 | XMLNS_ATOM = 'http://www.w3.org/2005/Atom' |
474 | |
475 | LOG = logging.getLogger('nova.api.openstack.wsgi') |
476 | @@ -386,6 +387,8 @@ |
477 | link_node = xml_doc.createElement('atom:link') |
478 | link_node.setAttribute('rel', link['rel']) |
479 | link_node.setAttribute('href', link['href']) |
480 | + if 'type' in link: |
481 | + link_node.setAttribute('type', link['type']) |
482 | link_nodes.append(link_node) |
483 | return link_nodes |
484 | |
485 | |
486 | === modified file 'nova/tests/api/openstack/test_versions.py' |
487 | --- nova/tests/api/openstack/test_versions.py 2011-07-21 21:20:32 +0000 |
488 | +++ nova/tests/api/openstack/test_versions.py 2011-08-02 20:52:09 +0000 |
489 | @@ -16,21 +16,92 @@ |
490 | # under the License. |
491 | |
492 | import json |
493 | +import stubout |
494 | import webob |
495 | +import xml.etree.ElementTree |
496 | + |
497 | |
498 | from nova import context |
499 | from nova import test |
500 | from nova.tests.api.openstack import fakes |
501 | from nova.api.openstack import versions |
502 | from nova.api.openstack import views |
503 | +from nova.api.openstack import wsgi |
504 | + |
505 | +VERSIONS = { |
506 | + "v1.0": { |
507 | + "id": "v1.0", |
508 | + "status": "DEPRECATED", |
509 | + "updated": "2011-01-21T11:33:21Z", |
510 | + "links": [ |
511 | + { |
512 | + "rel": "describedby", |
513 | + "type": "application/pdf", |
514 | + "href": "http://docs.rackspacecloud.com/" |
515 | + "servers/api/v1.0/cs-devguide-20110125.pdf" |
516 | + }, |
517 | + { |
518 | + "rel": "describedby", |
519 | + "type": "application/vnd.sun.wadl+xml", |
520 | + "href": "http://docs.rackspacecloud.com/" |
521 | + "servers/api/v1.0/application.wadl" |
522 | + }, |
523 | + ], |
524 | + "media-types": [ |
525 | + { |
526 | + "base": "application/xml", |
527 | + "type": "application/vnd.openstack.compute-v1.0+xml" |
528 | + }, |
529 | + { |
530 | + "base": "application/json", |
531 | + "type": "application/vnd.openstack.compute-v1.0+json" |
532 | + } |
533 | + ], |
534 | + }, |
535 | + "v1.1": { |
536 | + "id": "v1.1", |
537 | + "status": "CURRENT", |
538 | + "updated": "2011-01-21T11:33:21Z", |
539 | + "links": [ |
540 | + { |
541 | + "rel": "describedby", |
542 | + "type": "application/pdf", |
543 | + "href": "http://docs.rackspacecloud.com/" |
544 | + "servers/api/v1.1/cs-devguide-20110125.pdf" |
545 | + }, |
546 | + { |
547 | + "rel": "describedby", |
548 | + "type": "application/vnd.sun.wadl+xml", |
549 | + "href": "http://docs.rackspacecloud.com/" |
550 | + "servers/api/v1.1/application.wadl" |
551 | + }, |
552 | + ], |
553 | + "media-types": [ |
554 | + { |
555 | + "base": "application/xml", |
556 | + "type": "application/vnd.openstack.compute-v1.1+xml" |
557 | + }, |
558 | + { |
559 | + "base": "application/json", |
560 | + "type": "application/vnd.openstack.compute-v1.1+json" |
561 | + } |
562 | + ], |
563 | + }, |
564 | +} |
565 | |
566 | |
567 | class VersionsTest(test.TestCase): |
568 | def setUp(self): |
569 | super(VersionsTest, self).setUp() |
570 | self.context = context.get_admin_context() |
571 | + self.stubs = stubout.StubOutForTesting() |
572 | + fakes.stub_out_auth(self.stubs) |
573 | + #Stub out VERSIONS |
574 | + self.old_versions = versions.VERSIONS |
575 | + versions.VERSIONS = VERSIONS |
576 | |
577 | def tearDown(self): |
578 | + versions.VERSIONS = self.old_versions |
579 | super(VersionsTest, self).tearDown() |
580 | |
581 | def test_get_version_list(self): |
582 | @@ -44,7 +115,7 @@ |
583 | { |
584 | "id": "v1.1", |
585 | "status": "CURRENT", |
586 | - "updated": "2011-07-18T11:30:00Z", |
587 | + "updated": "2011-01-21T11:33:21Z", |
588 | "links": [ |
589 | { |
590 | "rel": "self", |
591 | @@ -54,7 +125,7 @@ |
592 | { |
593 | "id": "v1.0", |
594 | "status": "DEPRECATED", |
595 | - "updated": "2010-10-09T11:30:00Z", |
596 | + "updated": "2011-01-21T11:33:21Z", |
597 | "links": [ |
598 | { |
599 | "rel": "self", |
600 | @@ -64,6 +135,183 @@ |
601 | ] |
602 | self.assertEqual(versions, expected) |
603 | |
604 | + def test_get_version_1_0_detail(self): |
605 | + req = webob.Request.blank('/v1.0/') |
606 | + req.accept = "application/json" |
607 | + res = req.get_response(fakes.wsgi_app()) |
608 | + self.assertEqual(res.status_int, 200) |
609 | + self.assertEqual(res.content_type, "application/json") |
610 | + version = json.loads(res.body) |
611 | + expected = { |
612 | + "version": { |
613 | + "id": "v1.0", |
614 | + "status": "DEPRECATED", |
615 | + "updated": "2011-01-21T11:33:21Z", |
616 | + "links": [ |
617 | + { |
618 | + "rel": "self", |
619 | + "href": "http://localhost/v1.0/" |
620 | + }, |
621 | + { |
622 | + "rel": "describedby", |
623 | + "type": "application/pdf", |
624 | + "href": "http://docs.rackspacecloud.com/" |
625 | + "servers/api/v1.0/cs-devguide-20110125.pdf" |
626 | + }, |
627 | + { |
628 | + "rel": "describedby", |
629 | + "type": "application/vnd.sun.wadl+xml", |
630 | + "href": "http://docs.rackspacecloud.com/" |
631 | + "servers/api/v1.0/application.wadl" |
632 | + } |
633 | + ], |
634 | + "media-types": [ |
635 | + { |
636 | + "base": "application/xml", |
637 | + "type": "application/" |
638 | + "vnd.openstack.compute-v1.0+xml" |
639 | + }, |
640 | + { |
641 | + "base": "application/json", |
642 | + "type": "application/" |
643 | + "vnd.openstack.compute-v1.0+json" |
644 | + } |
645 | + ] |
646 | + } |
647 | + } |
648 | + self.assertEqual(expected, version) |
649 | + |
650 | + def test_get_version_1_1_detail(self): |
651 | + req = webob.Request.blank('/v1.1/') |
652 | + req.accept = "application/json" |
653 | + res = req.get_response(fakes.wsgi_app()) |
654 | + self.assertEqual(res.status_int, 200) |
655 | + self.assertEqual(res.content_type, "application/json") |
656 | + version = json.loads(res.body) |
657 | + expected = { |
658 | + "version": { |
659 | + "id": "v1.1", |
660 | + "status": "CURRENT", |
661 | + "updated": "2011-01-21T11:33:21Z", |
662 | + "links": [ |
663 | + { |
664 | + "rel": "self", |
665 | + "href": "http://localhost/v1.1/" |
666 | + }, |
667 | + { |
668 | + "rel": "describedby", |
669 | + "type": "application/pdf", |
670 | + "href": "http://docs.rackspacecloud.com/" |
671 | + "servers/api/v1.1/cs-devguide-20110125.pdf" |
672 | + }, |
673 | + { |
674 | + "rel": "describedby", |
675 | + "type": "application/vnd.sun.wadl+xml", |
676 | + "href": "http://docs.rackspacecloud.com/" |
677 | + "servers/api/v1.1/application.wadl" |
678 | + } |
679 | + ], |
680 | + "media-types": [ |
681 | + { |
682 | + "base": "application/xml", |
683 | + "type": "application/" |
684 | + "vnd.openstack.compute-v1.1+xml" |
685 | + }, |
686 | + { |
687 | + "base": "application/json", |
688 | + "type": "application/" |
689 | + "vnd.openstack.compute-v1.1+json" |
690 | + } |
691 | + ] |
692 | + } |
693 | + } |
694 | + self.assertEqual(expected, version) |
695 | + |
696 | + def test_get_version_1_0_detail_xml(self): |
697 | + req = webob.Request.blank('/v1.0/') |
698 | + req.accept = "application/xml" |
699 | + res = req.get_response(fakes.wsgi_app()) |
700 | + self.assertEqual(res.status_int, 200) |
701 | + self.assertEqual(res.content_type, "application/xml") |
702 | + root = xml.etree.ElementTree.XML(res.body) |
703 | + self.assertEqual(root.tag.split('}')[1], "version") |
704 | + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) |
705 | + |
706 | + children = list(root) |
707 | + media_types = children[0] |
708 | + media_type_nodes = list(media_types) |
709 | + links = (children[1], children[2], children[3]) |
710 | + |
711 | + self.assertEqual(media_types.tag.split('}')[1], 'media-types') |
712 | + for media_node in media_type_nodes: |
713 | + self.assertEqual(media_node.tag.split('}')[1], 'media-type') |
714 | + |
715 | + expected = """ |
716 | + <version id="v1.0" status="DEPRECATED" |
717 | + updated="2011-01-21T11:33:21Z" |
718 | + xmlns="%s" |
719 | + xmlns:atom="http://www.w3.org/2005/Atom"> |
720 | + |
721 | + <media-types> |
722 | + <media-type base="application/xml" |
723 | + type="application/vnd.openstack.compute-v1.0+xml"/> |
724 | + <media-type base="application/json" |
725 | + type="application/vnd.openstack.compute-v1.0+json"/> |
726 | + </media-types> |
727 | + |
728 | + <atom:link href="http://localhost/v1.0/" |
729 | + rel="self"/> |
730 | + |
731 | + <atom:link href="http://docs.rackspacecloud.com/servers/ |
732 | + api/v1.0/cs-devguide-20110125.pdf" |
733 | + rel="describedby" |
734 | + type="application/pdf"/> |
735 | + |
736 | + <atom:link href="http://docs.rackspacecloud.com/servers/ |
737 | + api/v1.0/application.wadl" |
738 | + rel="describedby" |
739 | + type="application/vnd.sun.wadl+xml"/> |
740 | + </version>""".replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11 |
741 | + |
742 | + actual = res.body.replace(" ", "").replace("\n", "") |
743 | + self.assertEqual(expected, actual) |
744 | + |
745 | + def test_get_version_1_1_detail_xml(self): |
746 | + req = webob.Request.blank('/v1.1/') |
747 | + req.accept = "application/xml" |
748 | + res = req.get_response(fakes.wsgi_app()) |
749 | + self.assertEqual(res.status_int, 200) |
750 | + self.assertEqual(res.content_type, "application/xml") |
751 | + expected = """ |
752 | + <version id="v1.1" status="CURRENT" |
753 | + updated="2011-01-21T11:33:21Z" |
754 | + xmlns="%s" |
755 | + xmlns:atom="http://www.w3.org/2005/Atom"> |
756 | + |
757 | + <media-types> |
758 | + <media-type base="application/xml" |
759 | + type="application/vnd.openstack.compute-v1.1+xml"/> |
760 | + <media-type base="application/json" |
761 | + type="application/vnd.openstack.compute-v1.1+json"/> |
762 | + </media-types> |
763 | + |
764 | + <atom:link href="http://localhost/v1.1/" |
765 | + rel="self"/> |
766 | + |
767 | + <atom:link href="http://docs.rackspacecloud.com/servers/ |
768 | + api/v1.1/cs-devguide-20110125.pdf" |
769 | + rel="describedby" |
770 | + type="application/pdf"/> |
771 | + |
772 | + <atom:link href="http://docs.rackspacecloud.com/servers/ |
773 | + api/v1.1/application.wadl" |
774 | + rel="describedby" |
775 | + type="application/vnd.sun.wadl+xml"/> |
776 | + </version>""".replace(" ", "").replace("\n", "") % wsgi.XMLNS_V11 |
777 | + |
778 | + actual = res.body.replace(" ", "").replace("\n", "") |
779 | + self.assertEqual(expected, actual) |
780 | + |
781 | def test_get_version_list_xml(self): |
782 | req = webob.Request.blank('/') |
783 | req.accept = "application/xml" |
784 | @@ -71,18 +319,94 @@ |
785 | self.assertEqual(res.status_int, 200) |
786 | self.assertEqual(res.content_type, "application/xml") |
787 | |
788 | - expected = """<versions> |
789 | - <version id="v1.1" status="CURRENT" updated="2011-07-18T11:30:00Z"> |
790 | + expected = """ |
791 | + <versions xmlns="%s" xmlns:atom="%s"> |
792 | + <version id="v1.1" status="CURRENT" updated="2011-01-21T11:33:21Z"> |
793 | <atom:link href="http://localhost/v1.1/" rel="self"/> |
794 | </version> |
795 | <version id="v1.0" status="DEPRECATED" |
796 | - updated="2010-10-09T11:30:00Z"> |
797 | + updated="2011-01-21T11:33:21Z"> |
798 | <atom:link href="http://localhost/v1.0/" rel="self"/> |
799 | </version> |
800 | - </versions>""".replace(" ", "").replace("\n", "") |
801 | - |
802 | - actual = res.body.replace(" ", "").replace("\n", "") |
803 | - |
804 | + </versions>""".replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11, |
805 | + wsgi.XMLNS_ATOM) |
806 | + |
807 | + actual = res.body.replace(" ", "").replace("\n", "") |
808 | + |
809 | + self.assertEqual(expected, actual) |
810 | + |
811 | + def test_get_version_1_0_detail_atom(self): |
812 | + req = webob.Request.blank('/v1.0/') |
813 | + req.accept = "application/atom+xml" |
814 | + res = req.get_response(fakes.wsgi_app()) |
815 | + self.assertEqual(res.status_int, 200) |
816 | + self.assertEqual("application/atom+xml", res.content_type) |
817 | + expected = """ |
818 | + <feed xmlns="http://www.w3.org/2005/Atom"> |
819 | + <title type="text">About This Version</title> |
820 | + <updated>2011-01-21T11:33:21Z</updated> |
821 | + <id>http://localhost/v1.0/</id> |
822 | + <author> |
823 | + <name>Rackspace</name> |
824 | + <uri>http://www.rackspace.com/</uri> |
825 | + </author> |
826 | + <link href="http://localhost/v1.0/" rel="self"/> |
827 | + <entry> |
828 | + <id>http://localhost/v1.0/</id> |
829 | + <title type="text">Version v1.0</title> |
830 | + <updated>2011-01-21T11:33:21Z</updated> |
831 | + <link href="http://localhost/v1.0/" |
832 | + rel="self"/> |
833 | + <link href="http://docs.rackspacecloud.com/servers/ |
834 | + api/v1.0/cs-devguide-20110125.pdf" |
835 | + rel="describedby" type="application/pdf"/> |
836 | + <link href="http://docs.rackspacecloud.com/servers/ |
837 | + api/v1.0/application.wadl" |
838 | + rel="describedby" type="application/vnd.sun.wadl+xml"/> |
839 | + <content type="text"> |
840 | + Version v1.0 DEPRECATED (2011-01-21T11:33:21Z) |
841 | + </content> |
842 | + </entry> |
843 | + </feed>""".replace(" ", "").replace("\n", "") |
844 | + |
845 | + actual = res.body.replace(" ", "").replace("\n", "") |
846 | + self.assertEqual(expected, actual) |
847 | + |
848 | + def test_get_version_1_1_detail_atom(self): |
849 | + req = webob.Request.blank('/v1.1/') |
850 | + req.accept = "application/atom+xml" |
851 | + res = req.get_response(fakes.wsgi_app()) |
852 | + self.assertEqual(res.status_int, 200) |
853 | + self.assertEqual("application/atom+xml", res.content_type) |
854 | + expected = """ |
855 | + <feed xmlns="http://www.w3.org/2005/Atom"> |
856 | + <title type="text">About This Version</title> |
857 | + <updated>2011-01-21T11:33:21Z</updated> |
858 | + <id>http://localhost/v1.1/</id> |
859 | + <author> |
860 | + <name>Rackspace</name> |
861 | + <uri>http://www.rackspace.com/</uri> |
862 | + </author> |
863 | + <link href="http://localhost/v1.1/" rel="self"/> |
864 | + <entry> |
865 | + <id>http://localhost/v1.1/</id> |
866 | + <title type="text">Version v1.1</title> |
867 | + <updated>2011-01-21T11:33:21Z</updated> |
868 | + <link href="http://localhost/v1.1/" |
869 | + rel="self"/> |
870 | + <link href="http://docs.rackspacecloud.com/servers/ |
871 | + api/v1.1/cs-devguide-20110125.pdf" |
872 | + rel="describedby" type="application/pdf"/> |
873 | + <link href="http://docs.rackspacecloud.com/servers/ |
874 | + api/v1.1/application.wadl" |
875 | + rel="describedby" type="application/vnd.sun.wadl+xml"/> |
876 | + <content type="text"> |
877 | + Version v1.1 CURRENT (2011-01-21T11:33:21Z) |
878 | + </content> |
879 | + </entry> |
880 | + </feed>""".replace(" ", "").replace("\n", "") |
881 | + |
882 | + actual = res.body.replace(" ", "").replace("\n", "") |
883 | self.assertEqual(expected, actual) |
884 | |
885 | def test_get_version_list_atom(self): |
886 | @@ -95,7 +419,7 @@ |
887 | expected = """ |
888 | <feed xmlns="http://www.w3.org/2005/Atom"> |
889 | <title type="text">Available API Versions</title> |
890 | - <updated>2011-07-18T11:30:00Z</updated> |
891 | + <updated>2011-01-21T11:33:21Z</updated> |
892 | <id>http://localhost/</id> |
893 | <author> |
894 | <name>Rackspace</name> |
895 | @@ -105,19 +429,19 @@ |
896 | <entry> |
897 | <id>http://localhost/v1.1/</id> |
898 | <title type="text">Version v1.1</title> |
899 | - <updated>2011-07-18T11:30:00Z</updated> |
900 | + <updated>2011-01-21T11:33:21Z</updated> |
901 | <link href="http://localhost/v1.1/" rel="self"/> |
902 | <content type="text"> |
903 | - Version v1.1 CURRENT (2011-07-18T11:30:00Z) |
904 | + Version v1.1 CURRENT (2011-01-21T11:33:21Z) |
905 | </content> |
906 | </entry> |
907 | <entry> |
908 | <id>http://localhost/v1.0/</id> |
909 | <title type="text">Version v1.0</title> |
910 | - <updated>2010-10-09T11:30:00Z</updated> |
911 | + <updated>2011-01-21T11:33:21Z</updated> |
912 | <link href="http://localhost/v1.0/" rel="self"/> |
913 | <content type="text"> |
914 | - Version v1.0 DEPRECATED (2010-10-09T11:30:00Z) |
915 | + Version v1.0 DEPRECATED (2011-01-21T11:33:21Z) |
916 | </content> |
917 | </entry> |
918 | </feed> |
919 | @@ -127,28 +451,184 @@ |
920 | |
921 | self.assertEqual(expected, actual) |
922 | |
923 | + def test_multi_choice_image(self): |
924 | + req = webob.Request.blank('/images/1') |
925 | + req.accept = "application/json" |
926 | + res = req.get_response(fakes.wsgi_app()) |
927 | + self.assertEqual(res.status_int, 300) |
928 | + self.assertEqual(res.content_type, "application/json") |
929 | + |
930 | + expected = { |
931 | + "choices": [ |
932 | + { |
933 | + "id": "v1.1", |
934 | + "status": "CURRENT", |
935 | + "links": [ |
936 | + { |
937 | + "href": "http://localhost/v1.1/images/1", |
938 | + "rel": "self", |
939 | + }, |
940 | + ], |
941 | + "media-types": [ |
942 | + { |
943 | + "base": "application/xml", |
944 | + "type": "application/vnd.openstack.compute-v1.1+xml" |
945 | + }, |
946 | + { |
947 | + "base": "application/json", |
948 | + "type": "application/vnd.openstack.compute-v1.1+json" |
949 | + }, |
950 | + ], |
951 | + }, |
952 | + { |
953 | + "id": "v1.0", |
954 | + "status": "DEPRECATED", |
955 | + "links": [ |
956 | + { |
957 | + "href": "http://localhost/v1.0/images/1", |
958 | + "rel": "self", |
959 | + }, |
960 | + ], |
961 | + "media-types": [ |
962 | + { |
963 | + "base": "application/xml", |
964 | + "type": "application/vnd.openstack.compute-v1.0+xml" |
965 | + }, |
966 | + { |
967 | + "base": "application/json", |
968 | + "type": "application/vnd.openstack.compute-v1.0+json" |
969 | + }, |
970 | + ], |
971 | + }, |
972 | + ], } |
973 | + |
974 | + self.assertDictMatch(expected, json.loads(res.body)) |
975 | + |
976 | + def test_multi_choice_image_xml(self): |
977 | + req = webob.Request.blank('/images/1') |
978 | + req.accept = "application/xml" |
979 | + res = req.get_response(fakes.wsgi_app()) |
980 | + self.assertEqual(res.status_int, 300) |
981 | + self.assertEqual(res.content_type, "application/xml") |
982 | + |
983 | + expected = """ |
984 | + <choices xmlns="%s" xmlns:atom="%s"> |
985 | + <version id="v1.1" status="CURRENT"> |
986 | + <media-types> |
987 | + <media-type base="application/xml" |
988 | + type="application/vnd.openstack.compute-v1.1+xml"/> |
989 | + <media-type base="application/json" |
990 | + type="application/vnd.openstack.compute-v1.1+json"/> |
991 | + </media-types> |
992 | + <atom:link href="http://localhost/v1.1/images/1" rel="self"/> |
993 | + </version> |
994 | + <version id="v1.0" status="DEPRECATED"> |
995 | + <media-types> |
996 | + <media-type base="application/xml" |
997 | + type="application/vnd.openstack.compute-v1.0+xml"/> |
998 | + <media-type base="application/json" |
999 | + type="application/vnd.openstack.compute-v1.0+json"/> |
1000 | + </media-types> |
1001 | + <atom:link href="http://localhost/v1.0/images/1" rel="self"/> |
1002 | + </version> |
1003 | + </choices>""".replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11, |
1004 | + wsgi.XMLNS_ATOM) |
1005 | + |
1006 | + def test_multi_choice_server_atom(self): |
1007 | + """ |
1008 | + Make sure multi choice responses do not have content-type |
1009 | + application/atom+xml (should use default of json) |
1010 | + """ |
1011 | + req = webob.Request.blank('/servers/2') |
1012 | + req.accept = "application/atom+xml" |
1013 | + res = req.get_response(fakes.wsgi_app()) |
1014 | + self.assertEqual(res.status_int, 300) |
1015 | + self.assertEqual(res.content_type, "application/json") |
1016 | + |
1017 | + def test_multi_choice_server(self): |
1018 | + req = webob.Request.blank('/servers/2') |
1019 | + req.accept = "application/json" |
1020 | + res = req.get_response(fakes.wsgi_app()) |
1021 | + self.assertEqual(res.status_int, 300) |
1022 | + self.assertEqual(res.content_type, "application/json") |
1023 | + |
1024 | + expected = { |
1025 | + "choices": [ |
1026 | + { |
1027 | + "id": "v1.1", |
1028 | + "status": "CURRENT", |
1029 | + "links": [ |
1030 | + { |
1031 | + "href": "http://localhost/v1.1/servers/2", |
1032 | + "rel": "self", |
1033 | + }, |
1034 | + ], |
1035 | + "media-types": [ |
1036 | + { |
1037 | + "base": "application/xml", |
1038 | + "type": "application/vnd.openstack.compute-v1.1+xml" |
1039 | + }, |
1040 | + { |
1041 | + "base": "application/json", |
1042 | + "type": "application/vnd.openstack.compute-v1.1+json" |
1043 | + }, |
1044 | + ], |
1045 | + }, |
1046 | + { |
1047 | + "id": "v1.0", |
1048 | + "status": "DEPRECATED", |
1049 | + "links": [ |
1050 | + { |
1051 | + "href": "http://localhost/v1.0/servers/2", |
1052 | + "rel": "self", |
1053 | + }, |
1054 | + ], |
1055 | + "media-types": [ |
1056 | + { |
1057 | + "base": "application/xml", |
1058 | + "type": "application/vnd.openstack.compute-v1.0+xml" |
1059 | + }, |
1060 | + { |
1061 | + "base": "application/json", |
1062 | + "type": "application/vnd.openstack.compute-v1.0+json" |
1063 | + }, |
1064 | + ], |
1065 | + }, |
1066 | + ], } |
1067 | + |
1068 | + self.assertDictMatch(expected, json.loads(res.body)) |
1069 | + |
1070 | + |
1071 | +class VersionsViewBuilderTests(test.TestCase): |
1072 | def test_view_builder(self): |
1073 | base_url = "http://example.org/" |
1074 | |
1075 | version_data = { |
1076 | - "id": "3.2.1", |
1077 | - "status": "CURRENT", |
1078 | - "updated": "2011-07-18T11:30:00Z"} |
1079 | + "v3.2.1": { |
1080 | + "id": "3.2.1", |
1081 | + "status": "CURRENT", |
1082 | + "updated": "2011-07-18T11:30:00Z", |
1083 | + } |
1084 | + } |
1085 | |
1086 | expected = { |
1087 | - "id": "3.2.1", |
1088 | - "status": "CURRENT", |
1089 | - "updated": "2011-07-18T11:30:00Z", |
1090 | - "links": [ |
1091 | + "versions": [ |
1092 | { |
1093 | - "rel": "self", |
1094 | - "href": "http://example.org/3.2.1/", |
1095 | - }, |
1096 | - ], |
1097 | + "id": "3.2.1", |
1098 | + "status": "CURRENT", |
1099 | + "updated": "2011-07-18T11:30:00Z", |
1100 | + "links": [ |
1101 | + { |
1102 | + "rel": "self", |
1103 | + "href": "http://example.org/3.2.1/", |
1104 | + }, |
1105 | + ], |
1106 | + } |
1107 | + ] |
1108 | } |
1109 | |
1110 | builder = views.versions.ViewBuilder(base_url) |
1111 | - output = builder.build(version_data) |
1112 | + output = builder.build_versions(version_data) |
1113 | |
1114 | self.assertEqual(output, expected) |
1115 | |
1116 | @@ -163,7 +643,9 @@ |
1117 | |
1118 | self.assertEqual(actual, expected) |
1119 | |
1120 | - def test_xml_serializer(self): |
1121 | + |
1122 | +class VersionsSerializerTests(test.TestCase): |
1123 | + def test_versions_list_xml_serializer(self): |
1124 | versions_data = { |
1125 | 'versions': [ |
1126 | { |
1127 | @@ -180,20 +662,137 @@ |
1128 | ] |
1129 | } |
1130 | |
1131 | - expected = """ |
1132 | - <versions> |
1133 | - <version id="2.7.1" status="DEPRECATED" |
1134 | - updated="2011-07-18T11:30:00Z"> |
1135 | - <atom:link href="http://test/2.7.1" rel="self"/> |
1136 | - </version> |
1137 | - </versions>""".replace(" ", "").replace("\n", "") |
1138 | - |
1139 | - serializer = versions.VersionsXMLSerializer() |
1140 | - response = serializer.default(versions_data) |
1141 | - response = response.replace(" ", "").replace("\n", "") |
1142 | - self.assertEqual(expected, response) |
1143 | - |
1144 | - def test_atom_serializer(self): |
1145 | + serializer = versions.VersionsXMLSerializer() |
1146 | + response = serializer.index(versions_data) |
1147 | + |
1148 | + root = xml.etree.ElementTree.XML(response) |
1149 | + self.assertEqual(root.tag.split('}')[1], "versions") |
1150 | + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) |
1151 | + version = list(root)[0] |
1152 | + self.assertEqual(version.tag.split('}')[1], "version") |
1153 | + self.assertEqual(version.get('id'), |
1154 | + versions_data['versions'][0]['id']) |
1155 | + self.assertEqual(version.get('status'), |
1156 | + versions_data['versions'][0]['status']) |
1157 | + |
1158 | + link = list(version)[0] |
1159 | + |
1160 | + self.assertEqual(link.tag.split('}')[1], "link") |
1161 | + self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM) |
1162 | + for key, val in versions_data['versions'][0]['links'][0].items(): |
1163 | + self.assertEqual(link.get(key), val) |
1164 | + |
1165 | + def test_versions_multi_xml_serializer(self): |
1166 | + versions_data = { |
1167 | + 'choices': [ |
1168 | + { |
1169 | + "id": "2.7.1", |
1170 | + "updated": "2011-07-18T11:30:00Z", |
1171 | + "status": "DEPRECATED", |
1172 | + "media-types": VERSIONS['v1.1']['media-types'], |
1173 | + "links": [ |
1174 | + { |
1175 | + "rel": "self", |
1176 | + "href": "http://test/2.7.1/images", |
1177 | + }, |
1178 | + ], |
1179 | + }, |
1180 | + ] |
1181 | + } |
1182 | + |
1183 | + serializer = versions.VersionsXMLSerializer() |
1184 | + response = serializer.multi(versions_data) |
1185 | + |
1186 | + root = xml.etree.ElementTree.XML(response) |
1187 | + self.assertEqual(root.tag.split('}')[1], "choices") |
1188 | + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) |
1189 | + version = list(root)[0] |
1190 | + self.assertEqual(version.tag.split('}')[1], "version") |
1191 | + self.assertEqual(version.get('id'), versions_data['choices'][0]['id']) |
1192 | + self.assertEqual(version.get('status'), |
1193 | + versions_data['choices'][0]['status']) |
1194 | + |
1195 | + media_types = list(version)[0] |
1196 | + media_type_nodes = list(media_types) |
1197 | + self.assertEqual(media_types.tag.split('}')[1], "media-types") |
1198 | + |
1199 | + set_types = versions_data['choices'][0]['media-types'] |
1200 | + for i, type in enumerate(set_types): |
1201 | + node = media_type_nodes[i] |
1202 | + self.assertEqual(node.tag.split('}')[1], "media-type") |
1203 | + for key, val in set_types[i].items(): |
1204 | + self.assertEqual(node.get(key), val) |
1205 | + |
1206 | + link = list(version)[1] |
1207 | + |
1208 | + self.assertEqual(link.tag.split('}')[1], "link") |
1209 | + self.assertEqual(link.tag.split('}')[0].strip('{'), wsgi.XMLNS_ATOM) |
1210 | + for key, val in versions_data['choices'][0]['links'][0].items(): |
1211 | + self.assertEqual(link.get(key), val) |
1212 | + |
1213 | + def test_version_detail_xml_serializer(self): |
1214 | + version_data = { |
1215 | + "version": { |
1216 | + "id": "v1.0", |
1217 | + "status": "CURRENT", |
1218 | + "updated": "2011-01-21T11:33:21Z", |
1219 | + "links": [ |
1220 | + { |
1221 | + "rel": "self", |
1222 | + "href": "http://localhost/v1.0/" |
1223 | + }, |
1224 | + { |
1225 | + "rel": "describedby", |
1226 | + "type": "application/pdf", |
1227 | + "href": "http://docs.rackspacecloud.com/" |
1228 | + "servers/api/v1.0/cs-devguide-20110125.pdf" |
1229 | + }, |
1230 | + { |
1231 | + "rel": "describedby", |
1232 | + "type": "application/vnd.sun.wadl+xml", |
1233 | + "href": "http://docs.rackspacecloud.com/" |
1234 | + "servers/api/v1.0/application.wadl" |
1235 | + }, |
1236 | + ], |
1237 | + "media-types": [ |
1238 | + { |
1239 | + "base": "application/xml", |
1240 | + "type": "application/vnd.openstack.compute-v1.0+xml" |
1241 | + }, |
1242 | + { |
1243 | + "base": "application/json", |
1244 | + "type": "application/vnd.openstack.compute-v1.0+json" |
1245 | + } |
1246 | + ], |
1247 | + }, |
1248 | + } |
1249 | + |
1250 | + serializer = versions.VersionsXMLSerializer() |
1251 | + response = serializer.show(version_data) |
1252 | + |
1253 | + root = xml.etree.ElementTree.XML(response) |
1254 | + self.assertEqual(root.tag.split('}')[1], "version") |
1255 | + self.assertEqual(root.tag.split('}')[0].strip('{'), wsgi.XMLNS_V11) |
1256 | + |
1257 | + children = list(root) |
1258 | + media_types = children[0] |
1259 | + media_type_nodes = list(media_types) |
1260 | + links = (children[1], children[2], children[3]) |
1261 | + |
1262 | + self.assertEqual(media_types.tag.split('}')[1], 'media-types') |
1263 | + for i, media_node in enumerate(media_type_nodes): |
1264 | + self.assertEqual(media_node.tag.split('}')[1], 'media-type') |
1265 | + for key, val in version_data['version']['media-types'][i].items(): |
1266 | + self.assertEqual(val, media_node.get(key)) |
1267 | + |
1268 | + for i, link in enumerate(links): |
1269 | + self.assertEqual(link.tag.split('}')[0].strip('{'), |
1270 | + 'http://www.w3.org/2005/Atom') |
1271 | + self.assertEqual(link.tag.split('}')[1], 'link') |
1272 | + for key, val in version_data['version']['links'][i].items(): |
1273 | + self.assertEqual(val, link.get(key)) |
1274 | + |
1275 | + def test_versions_list_atom_serializer(self): |
1276 | versions_data = { |
1277 | 'versions': [ |
1278 | { |
1279 | @@ -210,45 +809,158 @@ |
1280 | ] |
1281 | } |
1282 | |
1283 | - expected = """ |
1284 | - <feed xmlns="http://www.w3.org/2005/Atom"> |
1285 | - <title type="text"> |
1286 | - Available API Versions |
1287 | - </title> |
1288 | - <updated> |
1289 | - 2011-07-20T11:40:00Z |
1290 | - </updated> |
1291 | - <id> |
1292 | - http://test/ |
1293 | - </id> |
1294 | - <author> |
1295 | - <name> |
1296 | - Rackspace |
1297 | - </name> |
1298 | - <uri> |
1299 | - http://www.rackspace.com/ |
1300 | - </uri> |
1301 | - </author> |
1302 | - <link href="http://test/" rel="self"/> |
1303 | - <entry> |
1304 | - <id> |
1305 | - http://test/2.9.8 |
1306 | - </id> |
1307 | - <title type="text"> |
1308 | - Version 2.9.8 |
1309 | - </title> |
1310 | - <updated> |
1311 | - 2011-07-20T11:40:00Z |
1312 | - </updated> |
1313 | - <link href="http://test/2.9.8" rel="self"/> |
1314 | - <content type="text"> |
1315 | - Version 2.9.8 CURRENT (2011-07-20T11:40:00Z) |
1316 | - </content> |
1317 | - </entry> |
1318 | - </feed>""".replace(" ", "").replace("\n", "") |
1319 | - |
1320 | - serializer = versions.VersionsAtomSerializer() |
1321 | - response = serializer.default(versions_data) |
1322 | - print response |
1323 | - response = response.replace(" ", "").replace("\n", "") |
1324 | - self.assertEqual(expected, response) |
1325 | + serializer = versions.VersionsAtomSerializer() |
1326 | + response = serializer.index(versions_data) |
1327 | + |
1328 | + root = xml.etree.ElementTree.XML(response) |
1329 | + self.assertEqual(root.tag.split('}')[1], "feed") |
1330 | + self.assertEqual(root.tag.split('}')[0].strip('{'), |
1331 | + "http://www.w3.org/2005/Atom") |
1332 | + |
1333 | + children = list(root) |
1334 | + title = children[0] |
1335 | + updated = children[1] |
1336 | + id = children[2] |
1337 | + author = children[3] |
1338 | + link = children[4] |
1339 | + entry = children[5] |
1340 | + |
1341 | + self.assertEqual(title.tag.split('}')[1], 'title') |
1342 | + self.assertEqual(title.text, 'Available API Versions') |
1343 | + self.assertEqual(updated.tag.split('}')[1], 'updated') |
1344 | + self.assertEqual(updated.text, '2011-07-20T11:40:00Z') |
1345 | + self.assertEqual(id.tag.split('}')[1], 'id') |
1346 | + self.assertEqual(id.text, 'http://test/') |
1347 | + |
1348 | + self.assertEqual(author.tag.split('}')[1], 'author') |
1349 | + author_name = list(author)[0] |
1350 | + author_uri = list(author)[1] |
1351 | + self.assertEqual(author_name.tag.split('}')[1], 'name') |
1352 | + self.assertEqual(author_name.text, 'Rackspace') |
1353 | + self.assertEqual(author_uri.tag.split('}')[1], 'uri') |
1354 | + self.assertEqual(author_uri.text, 'http://www.rackspace.com/') |
1355 | + |
1356 | + self.assertEqual(link.get('href'), 'http://test/') |
1357 | + self.assertEqual(link.get('rel'), 'self') |
1358 | + |
1359 | + self.assertEqual(entry.tag.split('}')[1], 'entry') |
1360 | + entry_children = list(entry) |
1361 | + entry_id = entry_children[0] |
1362 | + entry_title = entry_children[1] |
1363 | + entry_updated = entry_children[2] |
1364 | + entry_link = entry_children[3] |
1365 | + entry_content = entry_children[4] |
1366 | + self.assertEqual(entry_id.tag.split('}')[1], "id") |
1367 | + self.assertEqual(entry_id.text, "http://test/2.9.8") |
1368 | + self.assertEqual(entry_title.tag.split('}')[1], "title") |
1369 | + self.assertEqual(entry_title.get('type'), "text") |
1370 | + self.assertEqual(entry_title.text, "Version 2.9.8") |
1371 | + self.assertEqual(entry_updated.tag.split('}')[1], "updated") |
1372 | + self.assertEqual(entry_updated.text, "2011-07-20T11:40:00Z") |
1373 | + self.assertEqual(entry_link.tag.split('}')[1], "link") |
1374 | + self.assertEqual(entry_link.get('href'), "http://test/2.9.8") |
1375 | + self.assertEqual(entry_link.get('rel'), "self") |
1376 | + self.assertEqual(entry_content.tag.split('}')[1], "content") |
1377 | + self.assertEqual(entry_content.get('type'), "text") |
1378 | + self.assertEqual(entry_content.text, |
1379 | + "Version 2.9.8 CURRENT (2011-07-20T11:40:00Z)") |
1380 | + |
1381 | + def test_version_detail_atom_serializer(self): |
1382 | + versions_data = { |
1383 | + "version": { |
1384 | + "id": "v1.1", |
1385 | + "status": "CURRENT", |
1386 | + "updated": "2011-01-21T11:33:21Z", |
1387 | + "links": [ |
1388 | + { |
1389 | + "rel": "self", |
1390 | + "href": "http://localhost/v1.1/" |
1391 | + }, |
1392 | + { |
1393 | + "rel": "describedby", |
1394 | + "type": "application/pdf", |
1395 | + "href": "http://docs.rackspacecloud.com/" |
1396 | + "servers/api/v1.1/cs-devguide-20110125.pdf" |
1397 | + }, |
1398 | + { |
1399 | + "rel": "describedby", |
1400 | + "type": "application/vnd.sun.wadl+xml", |
1401 | + "href": "http://docs.rackspacecloud.com/" |
1402 | + "servers/api/v1.1/application.wadl" |
1403 | + }, |
1404 | + ], |
1405 | + "media-types": [ |
1406 | + { |
1407 | + "base": "application/xml", |
1408 | + "type": "application/vnd.openstack.compute-v1.1+xml" |
1409 | + }, |
1410 | + { |
1411 | + "base": "application/json", |
1412 | + "type": "application/vnd.openstack.compute-v1.1+json" |
1413 | + } |
1414 | + ], |
1415 | + }, |
1416 | + } |
1417 | + |
1418 | + serializer = versions.VersionsAtomSerializer() |
1419 | + response = serializer.show(versions_data) |
1420 | + |
1421 | + root = xml.etree.ElementTree.XML(response) |
1422 | + self.assertEqual(root.tag.split('}')[1], "feed") |
1423 | + self.assertEqual(root.tag.split('}')[0].strip('{'), |
1424 | + "http://www.w3.org/2005/Atom") |
1425 | + |
1426 | + children = list(root) |
1427 | + title = children[0] |
1428 | + updated = children[1] |
1429 | + id = children[2] |
1430 | + author = children[3] |
1431 | + link = children[4] |
1432 | + entry = children[5] |
1433 | + |
1434 | + self.assertEqual(root.tag.split('}')[1], 'feed') |
1435 | + self.assertEqual(title.tag.split('}')[1], 'title') |
1436 | + self.assertEqual(title.text, 'About This Version') |
1437 | + self.assertEqual(updated.tag.split('}')[1], 'updated') |
1438 | + self.assertEqual(updated.text, '2011-01-21T11:33:21Z') |
1439 | + self.assertEqual(id.tag.split('}')[1], 'id') |
1440 | + self.assertEqual(id.text, 'http://localhost/v1.1/') |
1441 | + |
1442 | + self.assertEqual(author.tag.split('}')[1], 'author') |
1443 | + author_name = list(author)[0] |
1444 | + author_uri = list(author)[1] |
1445 | + self.assertEqual(author_name.tag.split('}')[1], 'name') |
1446 | + self.assertEqual(author_name.text, 'Rackspace') |
1447 | + self.assertEqual(author_uri.tag.split('}')[1], 'uri') |
1448 | + self.assertEqual(author_uri.text, 'http://www.rackspace.com/') |
1449 | + |
1450 | + self.assertEqual(link.get('href'), |
1451 | + 'http://localhost/v1.1/') |
1452 | + self.assertEqual(link.get('rel'), 'self') |
1453 | + |
1454 | + self.assertEqual(entry.tag.split('}')[1], 'entry') |
1455 | + entry_children = list(entry) |
1456 | + entry_id = entry_children[0] |
1457 | + entry_title = entry_children[1] |
1458 | + entry_updated = entry_children[2] |
1459 | + entry_links = (entry_children[3], entry_children[4], entry_children[5]) |
1460 | + entry_content = entry_children[6] |
1461 | + |
1462 | + self.assertEqual(entry_id.tag.split('}')[1], "id") |
1463 | + self.assertEqual(entry_id.text, |
1464 | + "http://localhost/v1.1/") |
1465 | + self.assertEqual(entry_title.tag.split('}')[1], "title") |
1466 | + self.assertEqual(entry_title.get('type'), "text") |
1467 | + self.assertEqual(entry_title.text, "Version v1.1") |
1468 | + self.assertEqual(entry_updated.tag.split('}')[1], "updated") |
1469 | + self.assertEqual(entry_updated.text, "2011-01-21T11:33:21Z") |
1470 | + |
1471 | + for i, link in enumerate(versions_data["version"]["links"]): |
1472 | + self.assertEqual(entry_links[i].tag.split('}')[1], "link") |
1473 | + for key, val in versions_data["version"]["links"][i].items(): |
1474 | + self.assertEqual(entry_links[i].get(key), val) |
1475 | + |
1476 | + self.assertEqual(entry_content.tag.split('}')[1], "content") |
1477 | + self.assertEqual(entry_content.get('type'), "text") |
1478 | + self.assertEqual(entry_content.text, |
1479 | + "Version v1.1 CURRENT (2011-01-21T11:33:21Z)") |
Can you keep all of the XMLNS constant strings in nova.api. openstack. wsgi?