Merge lp:~rackspace-titan/nova/images-metadata-container-lp795685 into lp:~hudson-openstack/nova/trunk
- images-metadata-container-lp795685
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Dan Prince |
Approved revision: | 1217 |
Merged at revision: | 1231 |
Proposed branch: | lp:~rackspace-titan/nova/images-metadata-container-lp795685 |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
1025 lines (+629/-181) 5 files modified
nova/api/openstack/image_metadata.py (+10/-8) nova/api/openstack/images.py (+71/-13) nova/api/openstack/views/images.py (+3/-0) nova/tests/api/openstack/test_image_metadata.py (+133/-89) nova/tests/api/openstack/test_images.py (+412/-71) |
To merge this branch: | bzr merge lp:~rackspace-titan/nova/images-metadata-container-lp795685 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dan Prince (community) | Approve | ||
Ed Leafe (community) | Approve | ||
Alex Meade (community) | Approve | ||
Brian Waldon | Pending | ||
Review via email: mp+65719@code.launchpad.net |
Commit message
Description of the change
- add metadata container to /images/detail and /images/<id> responses
- update xml serialization to encode image entities properly
Vish Ishaya (vishvananda) wrote : | # |
Syntax looks good, but as I am not as versed in the OSAPI i'm requesting a review from one who is :)
Brian Waldon (bcwaldon) wrote : | # |
> Syntax looks good, but as I am not as versed in the OSAPI i'm requesting a
> review from one who is :)
I'm flattered that you would ask me to review this, but I probably shouldn't as I am the one who proposed it.
Vish Ishaya (vishvananda) wrote : | # |
Haha, oops
Brian Waldon (bcwaldon) wrote : | # |
Alex: I added a test for serializing zero images, an image with zero metadata items, and a malformed image (no metadata dict). I accidentally merged trunk and added the tests in the same commit, so it may be hard to read individual revisions.
Alex Meade (alex-meade) wrote : | # |
Looks good Waldon
Ed Leafe (ed-leafe) wrote : | # |
Line 17: are you certain that 'value' will never contain non-ASCII characters? If not, using str() will cause an error without an explicit encoding.
Brian Waldon (bcwaldon) wrote : | # |
I think it is pretty safe to assume we will always have ascii characters, as the values will have been added through the api. I did add some more testing, though.
Ed Leafe (ed-leafe) wrote : | # |
lgtm with the encoding changes.
Dan Prince (dan-prince) wrote : | # |
Brian,
Looks good. I'm getting the following conflicts with nova trunk:
Text conflict in nova/api/
Text conflict in nova/tests/
Dan Prince (dan-prince) wrote : | # |
> Brian,
>
> Looks good. I'm getting the following conflicts with nova trunk:
>
> Text conflict in nova/api/
> Text conflict in nova/tests/
Oops. Disregard. Wrong branch.
Dan Prince (dan-prince) wrote : | # |
Looks like test_images actually does have a conflict:
Text conflict in nova/tests/
Brian Waldon (bcwaldon) wrote : | # |
Thanks for catching that, Dan. It's good now.
Dan Prince (dan-prince) : | # |
Preview Diff
1 | === modified file 'nova/api/openstack/image_metadata.py' |
2 | --- nova/api/openstack/image_metadata.py 2011-06-24 12:01:51 +0000 |
3 | +++ nova/api/openstack/image_metadata.py 2011-06-30 12:56:35 +0000 |
4 | @@ -112,18 +112,18 @@ |
5 | |
6 | |
7 | class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): |
8 | - def __init__(self): |
9 | - xmlns = wsgi.XMLNS_V11 |
10 | + def __init__(self, xmlns=wsgi.XMLNS_V11): |
11 | super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns) |
12 | |
13 | def _meta_item_to_xml(self, doc, key, value): |
14 | node = doc.createElement('meta') |
15 | - node.setAttribute('key', key) |
16 | - text = doc.createTextNode(value) |
17 | + doc.appendChild(node) |
18 | + node.setAttribute('key', '%s' % key) |
19 | + text = doc.createTextNode('%s' % value) |
20 | node.appendChild(text) |
21 | return node |
22 | |
23 | - def _meta_list_to_xml(self, xml_doc, meta_items): |
24 | + def meta_list_to_xml(self, xml_doc, meta_items): |
25 | container_node = xml_doc.createElement('metadata') |
26 | for (key, value) in meta_items: |
27 | item_node = self._meta_item_to_xml(xml_doc, key, value) |
28 | @@ -133,9 +133,10 @@ |
29 | def _meta_list_to_xml_string(self, metadata_dict): |
30 | xml_doc = minidom.Document() |
31 | items = metadata_dict['metadata'].items() |
32 | - container_node = self._meta_list_to_xml(xml_doc, items) |
33 | + container_node = self.meta_list_to_xml(xml_doc, items) |
34 | + xml_doc.appendChild(container_node) |
35 | self._add_xmlns(container_node) |
36 | - return container_node.toprettyxml(indent=' ') |
37 | + return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') |
38 | |
39 | def index(self, metadata_dict): |
40 | return self._meta_list_to_xml_string(metadata_dict) |
41 | @@ -147,8 +148,9 @@ |
42 | xml_doc = minidom.Document() |
43 | item_key, item_value = meta_item_dict.items()[0] |
44 | item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) |
45 | + xml_doc.appendChild(item_node) |
46 | self._add_xmlns(item_node) |
47 | - return item_node.toprettyxml(indent=' ') |
48 | + return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') |
49 | |
50 | def show(self, meta_item_dict): |
51 | return self._meta_item_to_xml_string(meta_item_dict['meta']) |
52 | |
53 | === modified file 'nova/api/openstack/images.py' |
54 | --- nova/api/openstack/images.py 2011-06-29 19:24:32 +0000 |
55 | +++ nova/api/openstack/images.py 2011-06-30 12:56:35 +0000 |
56 | @@ -16,6 +16,7 @@ |
57 | import os.path |
58 | |
59 | import webob.exc |
60 | +from xml.dom import minidom |
61 | |
62 | from nova import compute |
63 | from nova import exception |
64 | @@ -25,6 +26,7 @@ |
65 | from nova import utils |
66 | from nova.api.openstack import common |
67 | from nova.api.openstack import faults |
68 | +from nova.api.openstack import image_metadata |
69 | from nova.api.openstack.views import images as images_view |
70 | from nova.api.openstack import wsgi |
71 | |
72 | @@ -260,28 +262,84 @@ |
73 | return {'instance_ref': server_ref} |
74 | |
75 | |
76 | +class ImageXMLSerializer(wsgi.XMLDictSerializer): |
77 | + |
78 | + metadata = { |
79 | + "attributes": { |
80 | + "image": ["id", "name", "updated", "created", "status", |
81 | + "serverId", "progress", "serverRef"], |
82 | + "link": ["rel", "type", "href"], |
83 | + }, |
84 | + } |
85 | + |
86 | + xmlns = wsgi.XMLNS_V11 |
87 | + |
88 | + def __init__(self): |
89 | + self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer() |
90 | + |
91 | + def _image_to_xml(self, xml_doc, image): |
92 | + try: |
93 | + metadata = image.pop('metadata').items() |
94 | + except Exception: |
95 | + LOG.debug(_("Image object missing metadata attribute")) |
96 | + metadata = {} |
97 | + |
98 | + node = self._to_xml_node(xml_doc, self.metadata, 'image', image) |
99 | + metadata_node = self.metadata_serializer.meta_list_to_xml(xml_doc, |
100 | + metadata) |
101 | + node.appendChild(metadata_node) |
102 | + return node |
103 | + |
104 | + def _image_list_to_xml(self, xml_doc, images): |
105 | + container_node = xml_doc.createElement('images') |
106 | + for image in images: |
107 | + item_node = self._image_to_xml(xml_doc, image) |
108 | + container_node.appendChild(item_node) |
109 | + return container_node |
110 | + |
111 | + def _image_to_xml_string(self, image): |
112 | + xml_doc = minidom.Document() |
113 | + item_node = self._image_to_xml(xml_doc, image) |
114 | + self._add_xmlns(item_node) |
115 | + return item_node.toprettyxml(indent=' ') |
116 | + |
117 | + def _image_list_to_xml_string(self, images): |
118 | + xml_doc = minidom.Document() |
119 | + container_node = self._image_list_to_xml(xml_doc, images) |
120 | + self._add_xmlns(container_node) |
121 | + return container_node.toprettyxml(indent=' ') |
122 | + |
123 | + def detail(self, images_dict): |
124 | + return self._image_list_to_xml_string(images_dict['images']) |
125 | + |
126 | + def show(self, image_dict): |
127 | + return self._image_to_xml_string(image_dict['image']) |
128 | + |
129 | + def create(self, image_dict): |
130 | + return self._image_to_xml_string(image_dict['image']) |
131 | + |
132 | + |
133 | def create_resource(version='1.0'): |
134 | controller = { |
135 | '1.0': ControllerV10, |
136 | '1.1': ControllerV11, |
137 | }[version]() |
138 | |
139 | - xmlns = { |
140 | - '1.0': wsgi.XMLNS_V10, |
141 | - '1.1': wsgi.XMLNS_V11, |
142 | + metadata = { |
143 | + "attributes": { |
144 | + "image": ["id", "name", "updated", "created", "status", |
145 | + "serverId", "progress", "serverRef"], |
146 | + "link": ["rel", "type", "href"], |
147 | + }, |
148 | + } |
149 | + |
150 | + xml_serializer = { |
151 | + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), |
152 | + '1.1': ImageXMLSerializer(), |
153 | }[version] |
154 | |
155 | - metadata = { |
156 | - "attributes": { |
157 | - "image": ["id", "name", "updated", "created", "status", |
158 | - "serverId", "progress", "serverRef"], |
159 | - "link": ["rel", "type", "href"], |
160 | - }, |
161 | - } |
162 | - |
163 | serializers = { |
164 | - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, |
165 | - metadata=metadata), |
166 | + 'application/xml': xml_serializer, |
167 | } |
168 | |
169 | return wsgi.Resource(controller, serializers=serializers) |
170 | |
171 | === modified file 'nova/api/openstack/views/images.py' |
172 | --- nova/api/openstack/views/images.py 2011-06-24 12:01:51 +0000 |
173 | +++ nova/api/openstack/views/images.py 2011-06-30 12:56:35 +0000 |
174 | @@ -105,6 +105,9 @@ |
175 | image = ViewBuilder.build(self, image_obj, detail) |
176 | href = self.generate_href(image_obj["id"]) |
177 | |
178 | + if detail: |
179 | + image["metadata"] = image_obj.get("properties", {}) |
180 | + |
181 | image["links"] = [{ |
182 | "rel": "self", |
183 | "href": href, |
184 | |
185 | === modified file 'nova/tests/api/openstack/test_image_metadata.py' |
186 | --- nova/tests/api/openstack/test_image_metadata.py 2011-06-25 19:38:07 +0000 |
187 | +++ nova/tests/api/openstack/test_image_metadata.py 2011-06-30 12:56:35 +0000 |
188 | @@ -24,6 +24,7 @@ |
189 | |
190 | from nova import flags |
191 | from nova.api import openstack |
192 | +from nova import test |
193 | from nova.tests.api.openstack import fakes |
194 | import nova.wsgi |
195 | |
196 | @@ -31,7 +32,7 @@ |
197 | FLAGS = flags.FLAGS |
198 | |
199 | |
200 | -class ImageMetaDataTest(unittest.TestCase): |
201 | +class ImageMetaDataTest(test.TestCase): |
202 | |
203 | IMAGE_FIXTURES = [ |
204 | {'status': 'active', |
205 | @@ -112,30 +113,6 @@ |
206 | for (key, value) in res_dict['metadata'].items(): |
207 | self.assertEqual(value, res_dict['metadata'][key]) |
208 | |
209 | - def test_index_xml(self): |
210 | - serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
211 | - fixture = { |
212 | - 'metadata': { |
213 | - 'one': 'two', |
214 | - 'three': 'four', |
215 | - }, |
216 | - } |
217 | - output = serializer.index(fixture) |
218 | - actual = minidom.parseString(output.replace(" ", "")) |
219 | - |
220 | - expected = minidom.parseString(""" |
221 | - <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> |
222 | - <meta key="three"> |
223 | - four |
224 | - </meta> |
225 | - <meta key="one"> |
226 | - two |
227 | - </meta> |
228 | - </metadata> |
229 | - """.replace(" ", "")) |
230 | - |
231 | - self.assertEqual(expected.toxml(), actual.toxml()) |
232 | - |
233 | def test_show(self): |
234 | req = webob.Request.blank('/v1.1/images/1/meta/key1') |
235 | req.environ['api.version'] = '1.1' |
236 | @@ -146,24 +123,6 @@ |
237 | self.assertEqual(len(res_dict['meta']), 1) |
238 | self.assertEqual('value1', res_dict['meta']['key1']) |
239 | |
240 | - def test_show_xml(self): |
241 | - serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
242 | - fixture = { |
243 | - 'meta': { |
244 | - 'one': 'two', |
245 | - }, |
246 | - } |
247 | - output = serializer.show(fixture) |
248 | - actual = minidom.parseString(output.replace(" ", "")) |
249 | - |
250 | - expected = minidom.parseString(""" |
251 | - <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one"> |
252 | - two |
253 | - </meta> |
254 | - """.replace(" ", "")) |
255 | - |
256 | - self.assertEqual(expected.toxml(), actual.toxml()) |
257 | - |
258 | def test_show_not_found(self): |
259 | req = webob.Request.blank('/v1.1/images/1/meta/key9') |
260 | req.environ['api.version'] = '1.1' |
261 | @@ -185,34 +144,6 @@ |
262 | self.assertEqual('value2', res_dict['metadata']['key2']) |
263 | self.assertEqual(1, len(res_dict)) |
264 | |
265 | - def test_create_xml(self): |
266 | - serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
267 | - fixture = { |
268 | - 'metadata': { |
269 | - 'key9': 'value9', |
270 | - 'key2': 'value2', |
271 | - 'key1': 'value1', |
272 | - }, |
273 | - } |
274 | - output = serializer.create(fixture) |
275 | - actual = minidom.parseString(output.replace(" ", "")) |
276 | - |
277 | - expected = minidom.parseString(""" |
278 | - <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> |
279 | - <meta key="key2"> |
280 | - value2 |
281 | - </meta> |
282 | - <meta key="key9"> |
283 | - value9 |
284 | - </meta> |
285 | - <meta key="key1"> |
286 | - value1 |
287 | - </meta> |
288 | - </metadata> |
289 | - """.replace(" ", "")) |
290 | - |
291 | - self.assertEqual(expected.toxml(), actual.toxml()) |
292 | - |
293 | def test_update_item(self): |
294 | req = webob.Request.blank('/v1.1/images/1/meta/key1') |
295 | req.environ['api.version'] = '1.1' |
296 | @@ -235,24 +166,6 @@ |
297 | res = req.get_response(fakes.wsgi_app()) |
298 | self.assertEqual(400, res.status_int) |
299 | |
300 | - def test_update_item_xml(self): |
301 | - serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
302 | - fixture = { |
303 | - 'meta': { |
304 | - 'one': 'two', |
305 | - }, |
306 | - } |
307 | - output = serializer.update(fixture) |
308 | - actual = minidom.parseString(output.replace(" ", "")) |
309 | - |
310 | - expected = minidom.parseString(""" |
311 | - <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one"> |
312 | - two |
313 | - </meta> |
314 | - """.replace(" ", "")) |
315 | - |
316 | - self.assertEqual(expected.toxml(), actual.toxml()) |
317 | - |
318 | def test_update_item_too_many_keys(self): |
319 | req = webob.Request.blank('/v1.1/images/1/meta/key1') |
320 | req.environ['api.version'] = '1.1' |
321 | @@ -306,3 +219,134 @@ |
322 | req.headers["content-type"] = "application/json" |
323 | res = req.get_response(fakes.wsgi_app()) |
324 | self.assertEqual(400, res.status_int) |
325 | + |
326 | + |
327 | +class ImageMetadataXMLSerializationTest(test.TestCase): |
328 | + |
329 | + def test_index_xml(self): |
330 | + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
331 | + fixture = { |
332 | + 'metadata': { |
333 | + 'one': 'two', |
334 | + 'three': 'four', |
335 | + }, |
336 | + } |
337 | + output = serializer.serialize(fixture, 'index') |
338 | + actual = minidom.parseString(output.replace(" ", "")) |
339 | + |
340 | + expected = minidom.parseString(""" |
341 | + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> |
342 | + <meta key="three"> |
343 | + four |
344 | + </meta> |
345 | + <meta key="one"> |
346 | + two |
347 | + </meta> |
348 | + </metadata> |
349 | + """.replace(" ", "")) |
350 | + |
351 | + self.assertEqual(expected.toxml(), actual.toxml()) |
352 | + |
353 | + def test_index_xml_null(self): |
354 | + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
355 | + fixture = { |
356 | + 'metadata': { |
357 | + None: None, |
358 | + }, |
359 | + } |
360 | + output = serializer.serialize(fixture, 'index') |
361 | + actual = minidom.parseString(output.replace(" ", "")) |
362 | + |
363 | + expected = minidom.parseString(""" |
364 | + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> |
365 | + <meta key="None"> |
366 | + None |
367 | + </meta> |
368 | + </metadata> |
369 | + """.replace(" ", "")) |
370 | + |
371 | + self.assertEqual(expected.toxml(), actual.toxml()) |
372 | + |
373 | + def test_index_xml_unicode(self): |
374 | + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
375 | + fixture = { |
376 | + 'metadata': { |
377 | + u'three': u'Jos\xe9', |
378 | + }, |
379 | + } |
380 | + output = serializer.serialize(fixture, 'index') |
381 | + actual = minidom.parseString(output.replace(" ", "")) |
382 | + |
383 | + expected = minidom.parseString(u""" |
384 | + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> |
385 | + <meta key="three"> |
386 | + Jos\xe9 |
387 | + </meta> |
388 | + </metadata> |
389 | + """.encode("UTF-8").replace(" ", "")) |
390 | + |
391 | + self.assertEqual(expected.toxml(), actual.toxml()) |
392 | + |
393 | + def test_show_xml(self): |
394 | + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
395 | + fixture = { |
396 | + 'meta': { |
397 | + 'one': 'two', |
398 | + }, |
399 | + } |
400 | + output = serializer.serialize(fixture, 'show') |
401 | + actual = minidom.parseString(output.replace(" ", "")) |
402 | + |
403 | + expected = minidom.parseString(""" |
404 | + <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one"> |
405 | + two |
406 | + </meta> |
407 | + """.replace(" ", "")) |
408 | + |
409 | + self.assertEqual(expected.toxml(), actual.toxml()) |
410 | + |
411 | + def test_update_item_xml(self): |
412 | + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
413 | + fixture = { |
414 | + 'meta': { |
415 | + 'one': 'two', |
416 | + }, |
417 | + } |
418 | + output = serializer.serialize(fixture, 'update') |
419 | + actual = minidom.parseString(output.replace(" ", "")) |
420 | + |
421 | + expected = minidom.parseString(""" |
422 | + <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one"> |
423 | + two |
424 | + </meta> |
425 | + """.replace(" ", "")) |
426 | + |
427 | + self.assertEqual(expected.toxml(), actual.toxml()) |
428 | + |
429 | + def test_create_xml(self): |
430 | + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() |
431 | + fixture = { |
432 | + 'metadata': { |
433 | + 'key9': 'value9', |
434 | + 'key2': 'value2', |
435 | + 'key1': 'value1', |
436 | + }, |
437 | + } |
438 | + output = serializer.serialize(fixture, 'create') |
439 | + actual = minidom.parseString(output.replace(" ", "")) |
440 | + |
441 | + expected = minidom.parseString(""" |
442 | + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> |
443 | + <meta key="key2"> |
444 | + value2 |
445 | + </meta> |
446 | + <meta key="key9"> |
447 | + value9 |
448 | + </meta> |
449 | + <meta key="key1"> |
450 | + value1 |
451 | + </meta> |
452 | + </metadata> |
453 | + """.replace(" ", "")) |
454 | + |
455 | + self.assertEqual(expected.toxml(), actual.toxml()) |
456 | |
457 | === modified file 'nova/tests/api/openstack/test_images.py' |
458 | --- nova/tests/api/openstack/test_images.py 2011-06-29 19:24:32 +0000 |
459 | +++ nova/tests/api/openstack/test_images.py 2011-06-30 12:56:35 +0000 |
460 | @@ -394,20 +394,25 @@ |
461 | self.assertEqual(expected_image, actual_image) |
462 | |
463 | def test_get_image_v1_1(self): |
464 | - request = webob.Request.blank('/v1.1/images/123') |
465 | + request = webob.Request.blank('/v1.1/images/124') |
466 | response = request.get_response(fakes.wsgi_app()) |
467 | |
468 | actual_image = json.loads(response.body) |
469 | |
470 | - href = "http://localhost/v1.1/images/123" |
471 | + href = "http://localhost/v1.1/images/124" |
472 | |
473 | expected_image = { |
474 | "image": { |
475 | - "id": 123, |
476 | - "name": "public image", |
477 | + "id": 124, |
478 | + "name": "queued snapshot", |
479 | + "serverRef": "http://localhost/v1.1/servers/42", |
480 | "updated": self.NOW_API_FORMAT, |
481 | "created": self.NOW_API_FORMAT, |
482 | - "status": "ACTIVE", |
483 | + "status": "QUEUED", |
484 | + "metadata": { |
485 | + "instance_ref": "http://localhost/v1.1/servers/42", |
486 | + "user_id": "1", |
487 | + }, |
488 | "links": [{ |
489 | "rel": "self", |
490 | "href": href, |
491 | @@ -465,34 +470,6 @@ |
492 | |
493 | self.assertEqual(expected_image.toxml(), actual_image.toxml()) |
494 | |
495 | - def test_get_image_v1_1_xml(self): |
496 | - request = webob.Request.blank('/v1.1/images/123') |
497 | - request.accept = "application/xml" |
498 | - response = request.get_response(fakes.wsgi_app()) |
499 | - |
500 | - actual_image = minidom.parseString(response.body.replace(" ", "")) |
501 | - |
502 | - expected_href = "http://localhost/v1.1/images/123" |
503 | - expected_now = self.NOW_API_FORMAT |
504 | - expected_image = minidom.parseString(""" |
505 | - <image id="123" |
506 | - name="public image" |
507 | - updated="%(expected_now)s" |
508 | - created="%(expected_now)s" |
509 | - status="ACTIVE" |
510 | - xmlns="http://docs.openstack.org/compute/api/v1.1"> |
511 | - <links> |
512 | - <link href="%(expected_href)s" rel="self"/> |
513 | - <link href="%(expected_href)s" rel="bookmark" |
514 | - type="application/json" /> |
515 | - <link href="%(expected_href)s" rel="bookmark" |
516 | - type="application/xml" /> |
517 | - </links> |
518 | - </image> |
519 | - """.replace(" ", "") % (locals())) |
520 | - |
521 | - self.assertEqual(expected_image.toxml(), actual_image.toxml()) |
522 | - |
523 | def test_get_image_404_json(self): |
524 | request = webob.Request.blank('/v1.0/images/NonExistantImage') |
525 | response = request.get_response(fakes.wsgi_app()) |
526 | @@ -665,6 +642,7 @@ |
527 | expected = [{ |
528 | 'id': 123, |
529 | 'name': 'public image', |
530 | + 'metadata': {}, |
531 | 'updated': self.NOW_API_FORMAT, |
532 | 'created': self.NOW_API_FORMAT, |
533 | 'status': 'ACTIVE', |
534 | @@ -686,7 +664,11 @@ |
535 | { |
536 | 'id': 124, |
537 | 'name': 'queued snapshot', |
538 | - 'serverRef': "http://localhost:8774/v1.1/servers/42", |
539 | + 'metadata': { |
540 | + u'instance_ref': u'http://localhost/v1.1/servers/42', |
541 | + u'user_id': u'1', |
542 | + }, |
543 | + 'serverRef': "http://localhost/v1.1/servers/42", |
544 | 'updated': self.NOW_API_FORMAT, |
545 | 'created': self.NOW_API_FORMAT, |
546 | 'status': 'QUEUED', |
547 | @@ -708,7 +690,11 @@ |
548 | { |
549 | 'id': 125, |
550 | 'name': 'saving snapshot', |
551 | - 'serverRef': "http://localhost:8774/v1.1/servers/42", |
552 | + 'metadata': { |
553 | + u'instance_ref': u'http://localhost/v1.1/servers/42', |
554 | + u'user_id': u'1', |
555 | + }, |
556 | + 'serverRef': "http://localhost/v1.1/servers/42", |
557 | 'updated': self.NOW_API_FORMAT, |
558 | 'created': self.NOW_API_FORMAT, |
559 | 'status': 'SAVING', |
560 | @@ -731,7 +717,11 @@ |
561 | { |
562 | 'id': 126, |
563 | 'name': 'active snapshot', |
564 | - 'serverRef': "http://localhost:8774/v1.1/servers/42", |
565 | + 'metadata': { |
566 | + u'instance_ref': u'http://localhost/v1.1/servers/42', |
567 | + u'user_id': u'1', |
568 | + }, |
569 | + 'serverRef': "http://localhost/v1.1/servers/42", |
570 | 'updated': self.NOW_API_FORMAT, |
571 | 'created': self.NOW_API_FORMAT, |
572 | 'status': 'ACTIVE', |
573 | @@ -753,7 +743,11 @@ |
574 | { |
575 | 'id': 127, |
576 | 'name': 'killed snapshot', |
577 | - 'serverRef': "http://localhost:8774/v1.1/servers/42", |
578 | + 'metadata': { |
579 | + u'instance_ref': u'http://localhost/v1.1/servers/42', |
580 | + u'user_id': u'1', |
581 | + }, |
582 | + 'serverRef': "http://localhost/v1.1/servers/42", |
583 | 'updated': self.NOW_API_FORMAT, |
584 | 'created': self.NOW_API_FORMAT, |
585 | 'status': 'FAILED', |
586 | @@ -775,6 +769,7 @@ |
587 | { |
588 | 'id': 129, |
589 | 'name': None, |
590 | + 'metadata': {}, |
591 | 'updated': self.NOW_API_FORMAT, |
592 | 'created': self.NOW_API_FORMAT, |
593 | 'status': 'ACTIVE', |
594 | @@ -1108,39 +1103,6 @@ |
595 | response = req.get_response(fakes.wsgi_app()) |
596 | self.assertEqual(400, response.status_int) |
597 | |
598 | - def test_create_image_v1_1_xml_serialization(self): |
599 | - |
600 | - body = dict(image=dict(serverRef='123', name='Snapshot 1')) |
601 | - req = webob.Request.blank('/v1.1/images') |
602 | - req.method = 'POST' |
603 | - req.body = json.dumps(body) |
604 | - req.headers["content-type"] = "application/json" |
605 | - req.headers["accept"] = "application/xml" |
606 | - response = req.get_response(fakes.wsgi_app()) |
607 | - self.assertEqual(200, response.status_int) |
608 | - resp_xml = minidom.parseString(response.body.replace(" ", "")) |
609 | - expected_href = "http://localhost/v1.1/images/123" |
610 | - expected_image = minidom.parseString(""" |
611 | - <image |
612 | - created="None" |
613 | - id="123" |
614 | - name="Snapshot 1" |
615 | - serverRef="http://localhost/v1.1/servers/123" |
616 | - status="ACTIVE" |
617 | - updated="None" |
618 | - xmlns="http://docs.openstack.org/compute/api/v1.1"> |
619 | - <links> |
620 | - <link href="%(expected_href)s" rel="self"/> |
621 | - <link href="%(expected_href)s" rel="bookmark" |
622 | - type="application/json" /> |
623 | - <link href="%(expected_href)s" rel="bookmark" |
624 | - type="application/xml" /> |
625 | - </links> |
626 | - </image> |
627 | - """.replace(" ", "") % (locals())) |
628 | - |
629 | - self.assertEqual(expected_image.toxml(), resp_xml.toxml()) |
630 | - |
631 | def test_create_image_v1_1_no_server_ref(self): |
632 | |
633 | body = dict(image=dict(name='Snapshot 1')) |
634 | @@ -1171,7 +1133,7 @@ |
635 | image_id += 1 |
636 | |
637 | # Snapshot for User 1 |
638 | - server_ref = 'http://localhost:8774/v1.1/servers/42' |
639 | + server_ref = 'http://localhost/v1.1/servers/42' |
640 | snapshot_properties = {'instance_ref': server_ref, 'user_id': '1'} |
641 | for status in ('queued', 'saving', 'active', 'killed'): |
642 | add_fixture(id=image_id, name='%s snapshot' % status, |
643 | @@ -1193,3 +1155,382 @@ |
644 | image_id += 1 |
645 | |
646 | return fixtures |
647 | + |
648 | + |
649 | +class ImageXMLSerializationTest(test.TestCase): |
650 | + |
651 | + TIMESTAMP = "2010-10-11T10:30:22Z" |
652 | + SERVER_HREF = 'http://localhost/v1.1/servers/123' |
653 | + IMAGE_HREF = 'http://localhost/v1.1/images/%s' |
654 | + |
655 | + def test_show(self): |
656 | + serializer = images.ImageXMLSerializer() |
657 | + |
658 | + fixture = { |
659 | + 'image': { |
660 | + 'id': 1, |
661 | + 'name': 'Image1', |
662 | + 'created': self.TIMESTAMP, |
663 | + 'updated': self.TIMESTAMP, |
664 | + 'serverRef': self.SERVER_HREF, |
665 | + 'status': 'ACTIVE', |
666 | + 'metadata': { |
667 | + 'key1': 'value1', |
668 | + }, |
669 | + 'links': [ |
670 | + { |
671 | + 'href': self.IMAGE_HREF % (1,), |
672 | + 'rel': 'bookmark', |
673 | + 'type': 'application/json', |
674 | + }, |
675 | + ], |
676 | + }, |
677 | + } |
678 | + |
679 | + output = serializer.serialize(fixture, 'show') |
680 | + actual = minidom.parseString(output.replace(" ", "")) |
681 | + |
682 | + expected_server_href = self.SERVER_HREF |
683 | + expected_href = self.IMAGE_HREF % (1, ) |
684 | + expected_now = self.TIMESTAMP |
685 | + expected = minidom.parseString(""" |
686 | + <image id="1" |
687 | + name="Image1" |
688 | + serverRef="%(expected_server_href)s" |
689 | + updated="%(expected_now)s" |
690 | + created="%(expected_now)s" |
691 | + status="ACTIVE" |
692 | + xmlns="http://docs.openstack.org/compute/api/v1.1"> |
693 | + <links> |
694 | + <link href="%(expected_href)s" rel="bookmark" |
695 | + type="application/json" /> |
696 | + </links> |
697 | + <metadata> |
698 | + <meta key="key1"> |
699 | + value1 |
700 | + </meta> |
701 | + </metadata> |
702 | + </image> |
703 | + """.replace(" ", "") % (locals())) |
704 | + |
705 | + self.assertEqual(expected.toxml(), actual.toxml()) |
706 | + |
707 | + def test_show_zero_metadata(self): |
708 | + serializer = images.ImageXMLSerializer() |
709 | + |
710 | + fixture = { |
711 | + 'image': { |
712 | + 'id': 1, |
713 | + 'name': 'Image1', |
714 | + 'created': self.TIMESTAMP, |
715 | + 'updated': self.TIMESTAMP, |
716 | + 'serverRef': self.SERVER_HREF, |
717 | + 'status': 'ACTIVE', |
718 | + 'metadata': {}, |
719 | + 'links': [ |
720 | + { |
721 | + 'href': self.IMAGE_HREF % (1,), |
722 | + 'rel': 'bookmark', |
723 | + 'type': 'application/json', |
724 | + }, |
725 | + ], |
726 | + }, |
727 | + } |
728 | + |
729 | + output = serializer.serialize(fixture, 'show') |
730 | + actual = minidom.parseString(output.replace(" ", "")) |
731 | + |
732 | + expected_server_href = self.SERVER_HREF |
733 | + expected_href = self.IMAGE_HREF % (1, ) |
734 | + expected_now = self.TIMESTAMP |
735 | + expected = minidom.parseString(""" |
736 | + <image id="1" |
737 | + name="Image1" |
738 | + serverRef="%(expected_server_href)s" |
739 | + updated="%(expected_now)s" |
740 | + created="%(expected_now)s" |
741 | + status="ACTIVE" |
742 | + xmlns="http://docs.openstack.org/compute/api/v1.1"> |
743 | + <links> |
744 | + <link href="%(expected_href)s" rel="bookmark" |
745 | + type="application/json" /> |
746 | + </links> |
747 | + <metadata /> |
748 | + </image> |
749 | + """.replace(" ", "") % (locals())) |
750 | + |
751 | + self.assertEqual(expected.toxml(), actual.toxml()) |
752 | + |
753 | + def test_show_image_no_metadata_key(self): |
754 | + serializer = images.ImageXMLSerializer() |
755 | + |
756 | + fixture = { |
757 | + 'image': { |
758 | + 'id': 1, |
759 | + 'name': 'Image1', |
760 | + 'created': self.TIMESTAMP, |
761 | + 'updated': self.TIMESTAMP, |
762 | + 'serverRef': self.SERVER_HREF, |
763 | + 'status': 'ACTIVE', |
764 | + 'links': [ |
765 | + { |
766 | + 'href': self.IMAGE_HREF % (1,), |
767 | + 'rel': 'bookmark', |
768 | + 'type': 'application/json', |
769 | + }, |
770 | + ], |
771 | + |
772 | + }, |
773 | + } |
774 | + |
775 | + output = serializer.serialize(fixture, 'show') |
776 | + actual = minidom.parseString(output.replace(" ", "")) |
777 | + |
778 | + expected_server_href = self.SERVER_HREF |
779 | + expected_href = self.IMAGE_HREF % (1, ) |
780 | + expected_now = self.TIMESTAMP |
781 | + expected = minidom.parseString(""" |
782 | + <image id="1" |
783 | + name="Image1" |
784 | + serverRef="%(expected_server_href)s" |
785 | + updated="%(expected_now)s" |
786 | + created="%(expected_now)s" |
787 | + status="ACTIVE" |
788 | + xmlns="http://docs.openstack.org/compute/api/v1.1"> |
789 | + <links> |
790 | + <link href="%(expected_href)s" rel="bookmark" |
791 | + type="application/json" /> |
792 | + </links> |
793 | + <metadata /> |
794 | + </image> |
795 | + """.replace(" ", "") % (locals())) |
796 | + |
797 | + self.assertEqual(expected.toxml(), actual.toxml()) |
798 | + |
799 | + def test_index(self): |
800 | + serializer = images.ImageXMLSerializer() |
801 | + |
802 | + fixtures = { |
803 | + 'images': [ |
804 | + { |
805 | + 'id': 1, |
806 | + 'name': 'Image1', |
807 | + 'created': self.TIMESTAMP, |
808 | + 'updated': self.TIMESTAMP, |
809 | + 'serverRef': self.SERVER_HREF, |
810 | + 'status': 'ACTIVE', |
811 | + 'links': [ |
812 | + { |
813 | + 'href': 'http://localhost/v1.1/images/1', |
814 | + 'rel': 'bookmark', |
815 | + 'type': 'application/json', |
816 | + }, |
817 | + ], |
818 | + }, |
819 | + { |
820 | + 'id': 2, |
821 | + 'name': 'queued image', |
822 | + 'created': self.TIMESTAMP, |
823 | + 'updated': self.TIMESTAMP, |
824 | + 'serverRef': self.SERVER_HREF, |
825 | + 'status': 'QUEUED', |
826 | + 'links': [ |
827 | + { |
828 | + 'href': 'http://localhost/v1.1/images/2', |
829 | + 'rel': 'bookmark', |
830 | + 'type': 'application/json', |
831 | + }, |
832 | + ], |
833 | + }, |
834 | + ], |
835 | + } |
836 | + |
837 | + output = serializer.serialize(fixtures, 'index') |
838 | + actual = minidom.parseString(output.replace(" ", "")) |
839 | + |
840 | + expected_serverRef = self.SERVER_HREF |
841 | + expected_now = self.TIMESTAMP |
842 | + expected = minidom.parseString(""" |
843 | + <images xmlns="http://docs.openstack.org/compute/api/v1.1"> |
844 | + <image id="1" |
845 | + name="Image1" |
846 | + serverRef="%(expected_serverRef)s" |
847 | + updated="%(expected_now)s" |
848 | + created="%(expected_now)s" |
849 | + status="ACTIVE"> |
850 | + <links> |
851 | + <link href="http://localhost/v1.1/images/1" rel="bookmark" |
852 | + type="application/json" /> |
853 | + </links> |
854 | + </image> |
855 | + <image id="2" |
856 | + name="queued image" |
857 | + serverRef="%(expected_serverRef)s" |
858 | + updated="%(expected_now)s" |
859 | + created="%(expected_now)s" |
860 | + status="QUEUED"> |
861 | + <links> |
862 | + <link href="http://localhost/v1.1/images/2" rel="bookmark" |
863 | + type="application/json" /> |
864 | + </links> |
865 | + </image> |
866 | + </images> |
867 | + """.replace(" ", "") % (locals())) |
868 | + |
869 | + self.assertEqual(expected.toxml(), actual.toxml()) |
870 | + |
871 | + def test_index_zero_images(self): |
872 | + serializer = images.ImageXMLSerializer() |
873 | + |
874 | + fixtures = { |
875 | + 'images': [], |
876 | + } |
877 | + |
878 | + output = serializer.serialize(fixtures, 'index') |
879 | + actual = minidom.parseString(output.replace(" ", "")) |
880 | + |
881 | + expected_serverRef = self.SERVER_HREF |
882 | + expected_now = self.TIMESTAMP |
883 | + expected = minidom.parseString(""" |
884 | + <images xmlns="http://docs.openstack.org/compute/api/v1.1" /> |
885 | + """.replace(" ", "") % (locals())) |
886 | + |
887 | + self.assertEqual(expected.toxml(), actual.toxml()) |
888 | + |
889 | + def test_detail(self): |
890 | + serializer = images.ImageXMLSerializer() |
891 | + |
892 | + fixtures = { |
893 | + 'images': [ |
894 | + { |
895 | + 'id': 1, |
896 | + 'name': 'Image1', |
897 | + 'created': self.TIMESTAMP, |
898 | + 'updated': self.TIMESTAMP, |
899 | + 'serverRef': self.SERVER_HREF, |
900 | + 'status': 'ACTIVE', |
901 | + 'metadata': { |
902 | + 'key1': 'value1', |
903 | + 'key2': 'value2', |
904 | + }, |
905 | + 'links': [ |
906 | + { |
907 | + 'href': 'http://localhost/v1.1/images/1', |
908 | + 'rel': 'bookmark', |
909 | + 'type': 'application/json', |
910 | + }, |
911 | + ], |
912 | + }, |
913 | + { |
914 | + 'id': 2, |
915 | + 'name': 'queued image', |
916 | + 'created': self.TIMESTAMP, |
917 | + 'updated': self.TIMESTAMP, |
918 | + 'serverRef': self.SERVER_HREF, |
919 | + 'metadata': {}, |
920 | + 'status': 'QUEUED', |
921 | + 'links': [ |
922 | + { |
923 | + 'href': 'http://localhost/v1.1/images/2', |
924 | + 'rel': 'bookmark', |
925 | + 'type': 'application/json', |
926 | + }, |
927 | + ], |
928 | + }, |
929 | + ], |
930 | + } |
931 | + |
932 | + output = serializer.serialize(fixtures, 'detail') |
933 | + actual = minidom.parseString(output.replace(" ", "")) |
934 | + |
935 | + expected_serverRef = self.SERVER_HREF |
936 | + expected_now = self.TIMESTAMP |
937 | + expected = minidom.parseString(""" |
938 | + <images xmlns="http://docs.openstack.org/compute/api/v1.1"> |
939 | + <image id="1" |
940 | + name="Image1" |
941 | + serverRef="%(expected_serverRef)s" |
942 | + updated="%(expected_now)s" |
943 | + created="%(expected_now)s" |
944 | + status="ACTIVE"> |
945 | + <links> |
946 | + <link href="http://localhost/v1.1/images/1" rel="bookmark" |
947 | + type="application/json" /> |
948 | + </links> |
949 | + <metadata> |
950 | + <meta key="key2"> |
951 | + value2 |
952 | + </meta> |
953 | + <meta key="key1"> |
954 | + value1 |
955 | + </meta> |
956 | + </metadata> |
957 | + </image> |
958 | + <image id="2" |
959 | + name="queued image" |
960 | + serverRef="%(expected_serverRef)s" |
961 | + updated="%(expected_now)s" |
962 | + created="%(expected_now)s" |
963 | + status="QUEUED"> |
964 | + <links> |
965 | + <link href="http://localhost/v1.1/images/2" rel="bookmark" |
966 | + type="application/json" /> |
967 | + </links> |
968 | + <metadata /> |
969 | + </image> |
970 | + </images> |
971 | + """.replace(" ", "") % (locals())) |
972 | + |
973 | + self.assertEqual(expected.toxml(), actual.toxml()) |
974 | + |
975 | + def test_create(self): |
976 | + serializer = images.ImageXMLSerializer() |
977 | + |
978 | + fixture = { |
979 | + 'image': { |
980 | + 'id': 1, |
981 | + 'name': 'Image1', |
982 | + 'created': self.TIMESTAMP, |
983 | + 'updated': self.TIMESTAMP, |
984 | + 'serverRef': self.SERVER_HREF, |
985 | + 'status': 'ACTIVE', |
986 | + 'metadata': { |
987 | + 'key1': 'value1', |
988 | + }, |
989 | + 'links': [ |
990 | + { |
991 | + 'href': self.IMAGE_HREF % (1,), |
992 | + 'rel': 'bookmark', |
993 | + 'type': 'application/json', |
994 | + }, |
995 | + ], |
996 | + }, |
997 | + } |
998 | + |
999 | + output = serializer.serialize(fixture, 'create') |
1000 | + actual = minidom.parseString(output.replace(" ", "")) |
1001 | + |
1002 | + expected_server_href = self.SERVER_HREF |
1003 | + expected_href = self.IMAGE_HREF % (1, ) |
1004 | + expected_now = self.TIMESTAMP |
1005 | + expected = minidom.parseString(""" |
1006 | + <image id="1" |
1007 | + name="Image1" |
1008 | + serverRef="%(expected_server_href)s" |
1009 | + updated="%(expected_now)s" |
1010 | + created="%(expected_now)s" |
1011 | + status="ACTIVE" |
1012 | + xmlns="http://docs.openstack.org/compute/api/v1.1"> |
1013 | + <links> |
1014 | + <link href="%(expected_href)s" rel="bookmark" |
1015 | + type="application/json" /> |
1016 | + </links> |
1017 | + <metadata> |
1018 | + <meta key="key1"> |
1019 | + value1 |
1020 | + </meta> |
1021 | + </metadata> |
1022 | + </image> |
1023 | + """.replace(" ", "") % (locals())) |
1024 | + |
1025 | + self.assertEqual(expected.toxml(), actual.toxml()) |
Hey it all looks great. I do think there should be some negative test cases though.