Merge lp:~rackspace-titan/nova/servers-xml-serialization into lp:~hudson-openstack/nova/trunk

Proposed by Alex Meade
Status: Merged
Approved by: Brian Lamar
Approved revision: 1307
Merged at revision: 1343
Proposed branch: lp:~rackspace-titan/nova/servers-xml-serialization
Merge into: lp:~hudson-openstack/nova/trunk
Prerequisite: lp:~rackspace-titan/nova/servers-response-formatting
Diff against target: 1441 lines (+994/-295)
7 files modified
nova/api/openstack/common.py (+82/-0)
nova/api/openstack/image_metadata.py (+4/-84)
nova/api/openstack/images.py (+1/-1)
nova/api/openstack/servers.py (+125/-2)
nova/tests/api/openstack/test_common.py (+201/-0)
nova/tests/api/openstack/test_image_metadata.py (+0/-201)
nova/tests/api/openstack/test_servers.py (+581/-7)
To merge this branch: bzr merge lp:~rackspace-titan/nova/servers-xml-serialization
Reviewer Review Type Date Requested Status
Brian Lamar (community) Approve
Brian Waldon (community) Approve
Review via email: mp+68202@code.launchpad.net

Description of the change

Adds XML serialization for servers responses that match the current v1.1 spec

To post a comment you must log in.
Revision history for this message
Alex Meade (alex-meade) wrote :

Note that the ImageMetadataXMLSerializer was moved to MetadataXMLSerializer in common.py, no changes were made to the class but MetadataXMLSerializer should be cleaned up in a future merge.

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

> Note that the ImageMetadataXMLSerializer was moved to MetadataXMLSerializer in
> common.py, no changes were made to the class but MetadataXMLSerializer should
> be cleaned up in a future merge.

This also now goes for MetadataXMLDeserializer and MetadataHeadersSerializer

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

Great stuff alex. I do have a couple of comments:

118: unnecessary import

263: Can you make the MetadataXMLSerializer abstraction more like the MetadataXMLDeserializer? Right now, the deserializer lives in common while a deserializer class with a single extract_metadata method lives in wsgi. The serializer lives only in common, which forces you to use the whole class when you only need a single method (meta_list_to_xml).

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

>
> 263: Can you make the MetadataXMLSerializer abstraction more like the
> MetadataXMLDeserializer? Right now, the deserializer lives in common while a
> deserializer class with a single extract_metadata method lives in wsgi. The
> serializer lives only in common, which forces you to use the whole class when
> you only need a single method (meta_list_to_xml).

from an offline discussion, Waldon will take care of the this

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

Excellent.

review: Approve
1307. By Alex Meade

minor cleanup

Revision history for this message
Brian Lamar (blamar) wrote :

Looks good following the in-person suggestions and reaction :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/common.py'
2--- nova/api/openstack/common.py 2011-07-26 13:47:39 +0000
3+++ nova/api/openstack/common.py 2011-07-28 21:23:27 +0000
4@@ -17,12 +17,14 @@
5
6 import re
7 from urlparse import urlparse
8+from xml.dom import minidom
9
10 import webob
11
12 from nova import exception
13 from nova import flags
14 from nova import log as logging
15+from nova.api.openstack import wsgi
16
17
18 LOG = logging.getLogger('nova.api.openstack.common')
19@@ -192,3 +194,83 @@
20 except IndexError:
21 version = '1.0'
22 return version
23+
24+
25+class MetadataXMLDeserializer(wsgi.MetadataXMLDeserializer):
26+
27+ def _extract_metadata_container(self, datastring):
28+ dom = minidom.parseString(datastring)
29+ metadata_node = self.find_first_child_named(dom, "metadata")
30+ metadata = self.extract_metadata(metadata_node)
31+ return {'body': {'metadata': metadata}}
32+
33+ def create(self, datastring):
34+ return self._extract_metadata_container(datastring)
35+
36+ def update_all(self, datastring):
37+ return self._extract_metadata_container(datastring)
38+
39+ def update(self, datastring):
40+ dom = minidom.parseString(datastring)
41+ metadata_item = self.extract_metadata(dom)
42+ return {'body': {'meta': metadata_item}}
43+
44+
45+class MetadataHeadersSerializer(wsgi.ResponseHeadersSerializer):
46+
47+ def delete(self, response, data):
48+ response.status_int = 204
49+
50+
51+class MetadataXMLSerializer(wsgi.XMLDictSerializer):
52+ def __init__(self, xmlns=wsgi.XMLNS_V11):
53+ super(MetadataXMLSerializer, self).__init__(xmlns=xmlns)
54+
55+ def _meta_item_to_xml(self, doc, key, value):
56+ node = doc.createElement('meta')
57+ doc.appendChild(node)
58+ node.setAttribute('key', '%s' % key)
59+ text = doc.createTextNode('%s' % value)
60+ node.appendChild(text)
61+ return node
62+
63+ def meta_list_to_xml(self, xml_doc, meta_items):
64+ container_node = xml_doc.createElement('metadata')
65+ for (key, value) in meta_items:
66+ item_node = self._meta_item_to_xml(xml_doc, key, value)
67+ container_node.appendChild(item_node)
68+ return container_node
69+
70+ def _meta_list_to_xml_string(self, metadata_dict):
71+ xml_doc = minidom.Document()
72+ items = metadata_dict['metadata'].items()
73+ container_node = self.meta_list_to_xml(xml_doc, items)
74+ xml_doc.appendChild(container_node)
75+ self._add_xmlns(container_node)
76+ return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
77+
78+ def index(self, metadata_dict):
79+ return self._meta_list_to_xml_string(metadata_dict)
80+
81+ def create(self, metadata_dict):
82+ return self._meta_list_to_xml_string(metadata_dict)
83+
84+ def update_all(self, metadata_dict):
85+ return self._meta_list_to_xml_string(metadata_dict)
86+
87+ def _meta_item_to_xml_string(self, meta_item_dict):
88+ xml_doc = minidom.Document()
89+ item_key, item_value = meta_item_dict.items()[0]
90+ item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
91+ xml_doc.appendChild(item_node)
92+ self._add_xmlns(item_node)
93+ return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
94+
95+ def show(self, meta_item_dict):
96+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
97+
98+ def update(self, meta_item_dict):
99+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
100+
101+ def default(self, *args, **kwargs):
102+ return ''
103
104=== modified file 'nova/api/openstack/image_metadata.py'
105--- nova/api/openstack/image_metadata.py 2011-07-22 20:54:25 +0000
106+++ nova/api/openstack/image_metadata.py 2011-07-28 21:23:27 +0000
107@@ -16,12 +16,12 @@
108 # under the License.
109
110 from webob import exc
111-from xml.dom import minidom
112
113 from nova import flags
114 from nova import image
115 from nova import quota
116 from nova import utils
117+from nova.api.openstack import common
118 from nova.api.openstack import wsgi
119
120
121@@ -118,95 +118,15 @@
122 self.image_service.update(context, image_id, img, None)
123
124
125-class ImageMetadataXMLDeserializer(wsgi.MetadataXMLDeserializer):
126-
127- def _extract_metadata_container(self, datastring):
128- dom = minidom.parseString(datastring)
129- metadata_node = self.find_first_child_named(dom, "metadata")
130- metadata = self.extract_metadata(metadata_node)
131- return {'body': {'metadata': metadata}}
132-
133- def create(self, datastring):
134- return self._extract_metadata_container(datastring)
135-
136- def update_all(self, datastring):
137- return self._extract_metadata_container(datastring)
138-
139- def update(self, datastring):
140- dom = minidom.parseString(datastring)
141- metadata_item = self.extract_metadata(dom)
142- return {'body': {'meta': metadata_item}}
143-
144-
145-class HeadersSerializer(wsgi.ResponseHeadersSerializer):
146-
147- def delete(self, response, data):
148- response.status_int = 204
149-
150-
151-class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer):
152- def __init__(self, xmlns=wsgi.XMLNS_V11):
153- super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns)
154-
155- def _meta_item_to_xml(self, doc, key, value):
156- node = doc.createElement('meta')
157- doc.appendChild(node)
158- node.setAttribute('key', '%s' % key)
159- text = doc.createTextNode('%s' % value)
160- node.appendChild(text)
161- return node
162-
163- def meta_list_to_xml(self, xml_doc, meta_items):
164- container_node = xml_doc.createElement('metadata')
165- for (key, value) in meta_items:
166- item_node = self._meta_item_to_xml(xml_doc, key, value)
167- container_node.appendChild(item_node)
168- return container_node
169-
170- def _meta_list_to_xml_string(self, metadata_dict):
171- xml_doc = minidom.Document()
172- items = metadata_dict['metadata'].items()
173- container_node = self.meta_list_to_xml(xml_doc, items)
174- xml_doc.appendChild(container_node)
175- self._add_xmlns(container_node)
176- return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
177-
178- def index(self, metadata_dict):
179- return self._meta_list_to_xml_string(metadata_dict)
180-
181- def create(self, metadata_dict):
182- return self._meta_list_to_xml_string(metadata_dict)
183-
184- def update_all(self, metadata_dict):
185- return self._meta_list_to_xml_string(metadata_dict)
186-
187- def _meta_item_to_xml_string(self, meta_item_dict):
188- xml_doc = minidom.Document()
189- item_key, item_value = meta_item_dict.items()[0]
190- item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
191- xml_doc.appendChild(item_node)
192- self._add_xmlns(item_node)
193- return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
194-
195- def show(self, meta_item_dict):
196- return self._meta_item_to_xml_string(meta_item_dict['meta'])
197-
198- def update(self, meta_item_dict):
199- return self._meta_item_to_xml_string(meta_item_dict['meta'])
200-
201- def default(self, *args, **kwargs):
202- return ''
203-
204-
205 def create_resource():
206- headers_serializer = HeadersSerializer()
207+ headers_serializer = common.MetadataHeadersSerializer()
208
209 body_deserializers = {
210- 'application/xml': ImageMetadataXMLDeserializer(),
211+ 'application/xml': common.MetadataXMLDeserializer(),
212 }
213
214 body_serializers = {
215- 'application/xml': ImageMetadataXMLSerializer(),
216+ 'application/xml': common.MetadataXMLSerializer(),
217 }
218 serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer)
219 deserializer = wsgi.RequestDeserializer(body_deserializers)
220
221=== modified file 'nova/api/openstack/images.py'
222--- nova/api/openstack/images.py 2011-07-21 18:49:57 +0000
223+++ nova/api/openstack/images.py 2011-07-28 21:23:27 +0000
224@@ -284,7 +284,7 @@
225 xmlns = wsgi.XMLNS_V11
226
227 def __init__(self):
228- self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer()
229+ self.metadata_serializer = common.MetadataXMLSerializer()
230
231 def _image_to_xml(self, xml_doc, image):
232 image_node = xml_doc.createElement('image')
233
234=== modified file 'nova/api/openstack/servers.py'
235--- nova/api/openstack/servers.py 2011-07-28 18:59:10 +0000
236+++ nova/api/openstack/servers.py 2011-07-28 21:23:27 +0000
237@@ -18,6 +18,7 @@
238
239 from webob import exc
240 import webob
241+from xml.dom import minidom
242
243 from nova import compute
244 from nova import db
245@@ -27,6 +28,7 @@
246 from nova import utils
247 from nova.api.openstack import common
248 from nova.api.openstack import create_instance_helper as helper
249+from nova.api.openstack import ips
250 import nova.api.openstack.views.addresses
251 import nova.api.openstack.views.flavors
252 import nova.api.openstack.views.images
253@@ -608,6 +610,123 @@
254 response.status_int = 204
255
256
257+class ServerXMLSerializer(wsgi.XMLDictSerializer):
258+
259+ xmlns = wsgi.XMLNS_V11
260+
261+ def __init__(self):
262+ self.metadata_serializer = common.MetadataXMLSerializer()
263+ self.addresses_serializer = ips.IPXMLSerializer()
264+
265+ def _create_basic_entity_node(self, xml_doc, id, links, name):
266+ basic_node = xml_doc.createElement(name)
267+ basic_node.setAttribute('id', str(id))
268+ link_nodes = self._create_link_nodes(xml_doc, links)
269+ for link_node in link_nodes:
270+ basic_node.appendChild(link_node)
271+ return basic_node
272+
273+ def _create_metadata_node(self, xml_doc, metadata):
274+ return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata)
275+
276+ def _create_addresses_node(self, xml_doc, addresses):
277+ return self.addresses_serializer.networks_to_xml(xml_doc, addresses)
278+
279+ def _add_server_attributes(self, node, server):
280+ node.setAttribute('id', str(server['id']))
281+ node.setAttribute('uuid', str(server['uuid']))
282+ node.setAttribute('hostId', str(server['hostId']))
283+ node.setAttribute('name', server['name'])
284+ node.setAttribute('created', str(server['created']))
285+ node.setAttribute('updated', str(server['updated']))
286+ node.setAttribute('status', server['status'])
287+ if 'progress' in server:
288+ node.setAttribute('progress', str(server['progress']))
289+
290+ def _server_to_xml(self, xml_doc, server):
291+ server_node = xml_doc.createElement('server')
292+ server_node.setAttribute('id', str(server['id']))
293+ server_node.setAttribute('name', server['name'])
294+ link_nodes = self._create_link_nodes(xml_doc,
295+ server['links'])
296+ for link_node in link_nodes:
297+ server_node.appendChild(link_node)
298+ return server_node
299+
300+ def _server_to_xml_detailed(self, xml_doc, server):
301+ server_node = xml_doc.createElement('server')
302+ self._add_server_attributes(server_node, server)
303+
304+ link_nodes = self._create_link_nodes(xml_doc,
305+ server['links'])
306+ for link_node in link_nodes:
307+ server_node.appendChild(link_node)
308+
309+ if 'image' in server:
310+ image_node = self._create_basic_entity_node(xml_doc,
311+ server['image']['id'],
312+ server['image']['links'],
313+ 'image')
314+ server_node.appendChild(image_node)
315+
316+ if 'flavor' in server:
317+ flavor_node = self._create_basic_entity_node(xml_doc,
318+ server['flavor']['id'],
319+ server['flavor']['links'],
320+ 'flavor')
321+ server_node.appendChild(flavor_node)
322+
323+ metadata = server.get('metadata', {}).items()
324+ if len(metadata) > 0:
325+ metadata_node = self._create_metadata_node(xml_doc, metadata)
326+ server_node.appendChild(metadata_node)
327+
328+ addresses_node = self._create_addresses_node(xml_doc,
329+ server['addresses'])
330+ server_node.appendChild(addresses_node)
331+
332+ return server_node
333+
334+ def _server_list_to_xml(self, xml_doc, servers, detailed):
335+ container_node = xml_doc.createElement('servers')
336+ if detailed:
337+ server_to_xml = self._server_to_xml_detailed
338+ else:
339+ server_to_xml = self._server_to_xml
340+
341+ for server in servers:
342+ item_node = server_to_xml(xml_doc, server)
343+ container_node.appendChild(item_node)
344+ return container_node
345+
346+ def index(self, servers_dict):
347+ xml_doc = minidom.Document()
348+ node = self._server_list_to_xml(xml_doc,
349+ servers_dict['servers'],
350+ detailed=False)
351+ return self.to_xml_string(node, True)
352+
353+ def detail(self, servers_dict):
354+ xml_doc = minidom.Document()
355+ node = self._server_list_to_xml(xml_doc,
356+ servers_dict['servers'],
357+ detailed=True)
358+ return self.to_xml_string(node, True)
359+
360+ def show(self, server_dict):
361+ xml_doc = minidom.Document()
362+ node = self._server_to_xml_detailed(xml_doc,
363+ server_dict['server'])
364+ return self.to_xml_string(node, True)
365+
366+ def create(self, server_dict):
367+ xml_doc = minidom.Document()
368+ node = self._server_to_xml_detailed(xml_doc,
369+ server_dict['server'])
370+ node.setAttribute('adminPass', server_dict['server']['adminPass'])
371+ return self.to_xml_string(node, True)
372+
373+
374 def create_resource(version='1.0'):
375 controller = {
376 '1.0': ControllerV10,
377@@ -637,9 +756,13 @@
378
379 headers_serializer = HeadersSerializer()
380
381+ xml_serializer = {
382+ '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10),
383+ '1.1': ServerXMLSerializer(),
384+ }[version]
385+
386 body_serializers = {
387- 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
388- xmlns=xmlns),
389+ 'application/xml': xml_serializer,
390 }
391
392 body_deserializers = {
393
394=== modified file 'nova/tests/api/openstack/test_common.py'
395--- nova/tests/api/openstack/test_common.py 2011-07-26 13:47:39 +0000
396+++ nova/tests/api/openstack/test_common.py 2011-07-28 21:23:27 +0000
397@@ -20,6 +20,7 @@
398 """
399
400 import webob.exc
401+import xml.dom.minidom as minidom
402
403 from webob import Request
404
405@@ -265,3 +266,203 @@
406 expected = '1.0'
407 actual = common.get_version_from_href(fixture)
408 self.assertEqual(actual, expected)
409+
410+
411+class MetadataXMLDeserializationTest(test.TestCase):
412+
413+ deserializer = common.MetadataXMLDeserializer()
414+
415+ def test_create(self):
416+ request_body = """
417+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
418+ <meta key='123'>asdf</meta>
419+ <meta key='567'>jkl;</meta>
420+ </metadata>"""
421+ output = self.deserializer.deserialize(request_body, 'create')
422+ expected = {"body": {"metadata": {"123": "asdf", "567": "jkl;"}}}
423+ self.assertEquals(output, expected)
424+
425+ def test_create_empty(self):
426+ request_body = """
427+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"/>"""
428+ output = self.deserializer.deserialize(request_body, 'create')
429+ expected = {"body": {"metadata": {}}}
430+ self.assertEquals(output, expected)
431+
432+ def test_update_all(self):
433+ request_body = """
434+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
435+ <meta key='123'>asdf</meta>
436+ <meta key='567'>jkl;</meta>
437+ </metadata>"""
438+ output = self.deserializer.deserialize(request_body, 'update_all')
439+ expected = {"body": {"metadata": {"123": "asdf", "567": "jkl;"}}}
440+ self.assertEquals(output, expected)
441+
442+ def test_update(self):
443+ request_body = """
444+ <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
445+ key='123'>asdf</meta>"""
446+ output = self.deserializer.deserialize(request_body, 'update')
447+ expected = {"body": {"meta": {"123": "asdf"}}}
448+ self.assertEquals(output, expected)
449+
450+
451+class MetadataXMLSerializationTest(test.TestCase):
452+
453+ def test_index(self):
454+ serializer = common.MetadataXMLSerializer()
455+ fixture = {
456+ 'metadata': {
457+ 'one': 'two',
458+ 'three': 'four',
459+ },
460+ }
461+ output = serializer.serialize(fixture, 'index')
462+ actual = minidom.parseString(output.replace(" ", ""))
463+
464+ expected = minidom.parseString("""
465+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
466+ <meta key="three">
467+ four
468+ </meta>
469+ <meta key="one">
470+ two
471+ </meta>
472+ </metadata>
473+ """.replace(" ", ""))
474+
475+ self.assertEqual(expected.toxml(), actual.toxml())
476+
477+ def test_index_null(self):
478+ serializer = common.MetadataXMLSerializer()
479+ fixture = {
480+ 'metadata': {
481+ None: None,
482+ },
483+ }
484+ output = serializer.serialize(fixture, 'index')
485+ actual = minidom.parseString(output.replace(" ", ""))
486+
487+ expected = minidom.parseString("""
488+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
489+ <meta key="None">
490+ None
491+ </meta>
492+ </metadata>
493+ """.replace(" ", ""))
494+
495+ self.assertEqual(expected.toxml(), actual.toxml())
496+
497+ def test_index_unicode(self):
498+ serializer = common.MetadataXMLSerializer()
499+ fixture = {
500+ 'metadata': {
501+ u'three': u'Jos\xe9',
502+ },
503+ }
504+ output = serializer.serialize(fixture, 'index')
505+ actual = minidom.parseString(output.replace(" ", ""))
506+
507+ expected = minidom.parseString(u"""
508+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
509+ <meta key="three">
510+ Jos\xe9
511+ </meta>
512+ </metadata>
513+ """.encode("UTF-8").replace(" ", ""))
514+
515+ self.assertEqual(expected.toxml(), actual.toxml())
516+
517+ def test_show(self):
518+ serializer = common.MetadataXMLSerializer()
519+ fixture = {
520+ 'meta': {
521+ 'one': 'two',
522+ },
523+ }
524+ output = serializer.serialize(fixture, 'show')
525+ actual = minidom.parseString(output.replace(" ", ""))
526+
527+ expected = minidom.parseString("""
528+ <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
529+ two
530+ </meta>
531+ """.replace(" ", ""))
532+
533+ self.assertEqual(expected.toxml(), actual.toxml())
534+
535+ def test_update_all(self):
536+ serializer = common.MetadataXMLSerializer()
537+ fixture = {
538+ 'metadata': {
539+ 'key6': 'value6',
540+ 'key4': 'value4',
541+ },
542+ }
543+ output = serializer.serialize(fixture, 'update_all')
544+ actual = minidom.parseString(output.replace(" ", ""))
545+
546+ expected = minidom.parseString("""
547+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
548+ <meta key="key6">
549+ value6
550+ </meta>
551+ <meta key="key4">
552+ value4
553+ </meta>
554+ </metadata>
555+ """.replace(" ", ""))
556+
557+ self.assertEqual(expected.toxml(), actual.toxml())
558+
559+ def test_update_item(self):
560+ serializer = common.MetadataXMLSerializer()
561+ fixture = {
562+ 'meta': {
563+ 'one': 'two',
564+ },
565+ }
566+ output = serializer.serialize(fixture, 'update')
567+ actual = minidom.parseString(output.replace(" ", ""))
568+
569+ expected = minidom.parseString("""
570+ <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
571+ two
572+ </meta>
573+ """.replace(" ", ""))
574+
575+ self.assertEqual(expected.toxml(), actual.toxml())
576+
577+ def test_create(self):
578+ serializer = common.MetadataXMLSerializer()
579+ fixture = {
580+ 'metadata': {
581+ 'key9': 'value9',
582+ 'key2': 'value2',
583+ 'key1': 'value1',
584+ },
585+ }
586+ output = serializer.serialize(fixture, 'create')
587+ actual = minidom.parseString(output.replace(" ", ""))
588+
589+ expected = minidom.parseString("""
590+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
591+ <meta key="key2">
592+ value2
593+ </meta>
594+ <meta key="key9">
595+ value9
596+ </meta>
597+ <meta key="key1">
598+ value1
599+ </meta>
600+ </metadata>
601+ """.replace(" ", ""))
602+
603+ self.assertEqual(expected.toxml(), actual.toxml())
604+
605+ def test_delete(self):
606+ serializer = common.MetadataXMLSerializer()
607+ output = serializer.serialize(None, 'delete')
608+ self.assertEqual(output, '')
609
610=== modified file 'nova/tests/api/openstack/test_image_metadata.py'
611--- nova/tests/api/openstack/test_image_metadata.py 2011-07-22 20:54:25 +0000
612+++ nova/tests/api/openstack/test_image_metadata.py 2011-07-28 21:23:27 +0000
613@@ -19,7 +19,6 @@
614 import stubout
615 import unittest
616 import webob
617-import xml.dom.minidom as minidom
618
619
620 from nova import flags
621@@ -252,203 +251,3 @@
622 req.headers["content-type"] = "application/json"
623 res = req.get_response(fakes.wsgi_app())
624 self.assertEqual(400, res.status_int)
625-
626-
627-class ImageMetadataXMLDeserializationTest(test.TestCase):
628-
629- deserializer = openstack.image_metadata.ImageMetadataXMLDeserializer()
630-
631- def test_create(self):
632- request_body = """
633- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
634- <meta key='123'>asdf</meta>
635- <meta key='567'>jkl;</meta>
636- </metadata>"""
637- output = self.deserializer.deserialize(request_body, 'create')
638- expected = {"body": {"metadata": {"123": "asdf", "567": "jkl;"}}}
639- self.assertEquals(output, expected)
640-
641- def test_create_empty(self):
642- request_body = """
643- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"/>"""
644- output = self.deserializer.deserialize(request_body, 'create')
645- expected = {"body": {"metadata": {}}}
646- self.assertEquals(output, expected)
647-
648- def test_update_all(self):
649- request_body = """
650- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
651- <meta key='123'>asdf</meta>
652- <meta key='567'>jkl;</meta>
653- </metadata>"""
654- output = self.deserializer.deserialize(request_body, 'update_all')
655- expected = {"body": {"metadata": {"123": "asdf", "567": "jkl;"}}}
656- self.assertEquals(output, expected)
657-
658- def test_update(self):
659- request_body = """
660- <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
661- key='123'>asdf</meta>"""
662- output = self.deserializer.deserialize(request_body, 'update')
663- expected = {"body": {"meta": {"123": "asdf"}}}
664- self.assertEquals(output, expected)
665-
666-
667-class ImageMetadataXMLSerializationTest(test.TestCase):
668-
669- def test_index(self):
670- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
671- fixture = {
672- 'metadata': {
673- 'one': 'two',
674- 'three': 'four',
675- },
676- }
677- output = serializer.serialize(fixture, 'index')
678- actual = minidom.parseString(output.replace(" ", ""))
679-
680- expected = minidom.parseString("""
681- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
682- <meta key="three">
683- four
684- </meta>
685- <meta key="one">
686- two
687- </meta>
688- </metadata>
689- """.replace(" ", ""))
690-
691- self.assertEqual(expected.toxml(), actual.toxml())
692-
693- def test_index_null(self):
694- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
695- fixture = {
696- 'metadata': {
697- None: None,
698- },
699- }
700- output = serializer.serialize(fixture, 'index')
701- actual = minidom.parseString(output.replace(" ", ""))
702-
703- expected = minidom.parseString("""
704- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
705- <meta key="None">
706- None
707- </meta>
708- </metadata>
709- """.replace(" ", ""))
710-
711- self.assertEqual(expected.toxml(), actual.toxml())
712-
713- def test_index_unicode(self):
714- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
715- fixture = {
716- 'metadata': {
717- u'three': u'Jos\xe9',
718- },
719- }
720- output = serializer.serialize(fixture, 'index')
721- actual = minidom.parseString(output.replace(" ", ""))
722-
723- expected = minidom.parseString(u"""
724- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
725- <meta key="three">
726- Jos\xe9
727- </meta>
728- </metadata>
729- """.encode("UTF-8").replace(" ", ""))
730-
731- self.assertEqual(expected.toxml(), actual.toxml())
732-
733- def test_show(self):
734- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
735- fixture = {
736- 'meta': {
737- 'one': 'two',
738- },
739- }
740- output = serializer.serialize(fixture, 'show')
741- actual = minidom.parseString(output.replace(" ", ""))
742-
743- expected = minidom.parseString("""
744- <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
745- two
746- </meta>
747- """.replace(" ", ""))
748-
749- self.assertEqual(expected.toxml(), actual.toxml())
750-
751- def test_update_all(self):
752- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
753- fixture = {
754- 'metadata': {
755- 'key6': 'value6',
756- 'key4': 'value4',
757- },
758- }
759- output = serializer.serialize(fixture, 'update_all')
760- actual = minidom.parseString(output.replace(" ", ""))
761-
762- expected = minidom.parseString("""
763- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
764- <meta key="key6">
765- value6
766- </meta>
767- <meta key="key4">
768- value4
769- </meta>
770- </metadata>
771- """.replace(" ", ""))
772-
773- self.assertEqual(expected.toxml(), actual.toxml())
774-
775- def test_update_item(self):
776- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
777- fixture = {
778- 'meta': {
779- 'one': 'two',
780- },
781- }
782- output = serializer.serialize(fixture, 'update')
783- actual = minidom.parseString(output.replace(" ", ""))
784-
785- expected = minidom.parseString("""
786- <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
787- two
788- </meta>
789- """.replace(" ", ""))
790-
791- self.assertEqual(expected.toxml(), actual.toxml())
792-
793- def test_create(self):
794- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
795- fixture = {
796- 'metadata': {
797- 'key9': 'value9',
798- 'key2': 'value2',
799- 'key1': 'value1',
800- },
801- }
802- output = serializer.serialize(fixture, 'create')
803- actual = minidom.parseString(output.replace(" ", ""))
804-
805- expected = minidom.parseString("""
806- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
807- <meta key="key2">
808- value2
809- </meta>
810- <meta key="key9">
811- value9
812- </meta>
813- <meta key="key1">
814- value1
815- </meta>
816- </metadata>
817- """.replace(" ", ""))
818-
819- self.assertEqual(expected.toxml(), actual.toxml())
820-
821- def test_delete(self):
822- serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
823- output = serializer.serialize(None, 'delete')
824- self.assertEqual(output, '')
825
826=== modified file 'nova/tests/api/openstack/test_servers.py'
827--- nova/tests/api/openstack/test_servers.py 2011-07-28 20:58:57 +0000
828+++ nova/tests/api/openstack/test_servers.py 2011-07-28 21:23:27 +0000
829@@ -400,6 +400,78 @@
830
831 self.assertDictMatch(res_dict, expected_server)
832
833+ def test_get_server_by_id_v1_1_xml(self):
834+ image_bookmark = "http://localhost/images/10"
835+ flavor_ref = "http://localhost/v1.1/flavors/1"
836+ flavor_id = "1"
837+ flavor_bookmark = "http://localhost/flavors/1"
838+ server_href = "http://localhost/v1.1/servers/1"
839+ server_bookmark = "http://localhost/servers/1"
840+
841+ public_ip = '192.168.0.3'
842+ private_ip = '172.19.0.1'
843+ interfaces = [
844+ {
845+ 'network': {'label': 'public'},
846+ 'fixed_ips': [
847+ {'address': public_ip},
848+ ],
849+ },
850+ {
851+ 'network': {'label': 'private'},
852+ 'fixed_ips': [
853+ {'address': private_ip},
854+ ],
855+ },
856+ ]
857+ new_return_server = return_server_with_attributes(
858+ interfaces=interfaces)
859+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
860+
861+ req = webob.Request.blank('/v1.1/servers/1')
862+ req.headers['Accept'] = 'application/xml'
863+ res = req.get_response(fakes.wsgi_app())
864+ actual = minidom.parseString(res.body.replace(' ', ''))
865+ expected_uuid = FAKE_UUID
866+ expected_updated = "2010-11-11T11:00:00Z"
867+ expected_created = "2010-10-10T12:00:00Z"
868+ expected = minidom.parseString("""
869+ <server id="1"
870+ uuid="%(expected_uuid)s"
871+ xmlns="http://docs.openstack.org/compute/api/v1.1"
872+ xmlns:atom="http://www.w3.org/2005/Atom"
873+ name="server1"
874+ updated="%(expected_updated)s"
875+ created="%(expected_created)s"
876+ hostId=""
877+ status="BUILD"
878+ progress="0">
879+ <atom:link href="%(server_href)s" rel="self"/>
880+ <atom:link href="%(server_bookmark)s" rel="bookmark"/>
881+ <image id="10">
882+ <atom:link rel="bookmark" href="%(image_bookmark)s"/>
883+ </image>
884+ <flavor id="1">
885+ <atom:link rel="bookmark" href="%(flavor_bookmark)s"/>
886+ </flavor>
887+ <metadata>
888+ <meta key="seq">
889+ 1
890+ </meta>
891+ </metadata>
892+ <addresses>
893+ <network id="public">
894+ <ip version="4" addr="%(public_ip)s"/>
895+ </network>
896+ <network id="private">
897+ <ip version="4" addr="%(private_ip)s"/>
898+ </network>
899+ </addresses>
900+ </server>
901+ """.replace(" ", "") % (locals()))
902+
903+ self.assertEqual(expected.toxml(), actual.toxml())
904+
905 def test_get_server_with_active_status_by_id_v1_1(self):
906 image_bookmark = "http://localhost/images/10"
907 flavor_ref = "http://localhost/v1.1/flavors/1"
908@@ -3131,7 +3203,7 @@
909 address_builder,
910 flavor_builder,
911 image_builder,
912- base_url
913+ base_url,
914 )
915 return view_builder
916
917@@ -3287,12 +3359,12 @@
918 },
919 "flavor": {
920 "id": "1",
921- "links": [
922- {
923- "rel": "bookmark",
924- "href": flavor_bookmark,
925- },
926- ],
927+ "links": [
928+ {
929+ "rel": "bookmark",
930+ "href": flavor_bookmark,
931+ },
932+ ],
933 },
934 "addresses": {},
935 "metadata": {
936@@ -3314,3 +3386,505 @@
937
938 output = self.view_builder.build(self.instance, True)
939 self.assertDictMatch(output, expected_server)
940+
941+
942+class ServerXMLSerializationTest(test.TestCase):
943+
944+ TIMESTAMP = "2010-10-11T10:30:22Z"
945+ SERVER_HREF = 'http://localhost/v1.1/servers/123'
946+ SERVER_BOOKMARK = 'http://localhost/servers/123'
947+ IMAGE_BOOKMARK = 'http://localhost/images/5'
948+ FLAVOR_BOOKMARK = 'http://localhost/flavors/1'
949+
950+ def setUp(self):
951+ self.maxDiff = None
952+ test.TestCase.setUp(self)
953+
954+ def test_show(self):
955+ serializer = servers.ServerXMLSerializer()
956+
957+ fixture = {
958+ "server": {
959+ "id": 1,
960+ "uuid": FAKE_UUID,
961+ 'created': self.TIMESTAMP,
962+ 'updated': self.TIMESTAMP,
963+ "progress": 0,
964+ "name": "test_server",
965+ "status": "BUILD",
966+ "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
967+ "image": {
968+ "id": "5",
969+ "links": [
970+ {
971+ "rel": "bookmark",
972+ "href": self.IMAGE_BOOKMARK,
973+ },
974+ ],
975+ },
976+ "flavor": {
977+ "id": "1",
978+ "links": [
979+ {
980+ "rel": "bookmark",
981+ "href": self.FLAVOR_BOOKMARK,
982+ },
983+ ],
984+ },
985+ "addresses": {
986+ "network_one": [
987+ {
988+ "version": 4,
989+ "addr": "67.23.10.138",
990+ },
991+ {
992+ "version": 6,
993+ "addr": "::babe:67.23.10.138",
994+ },
995+ ],
996+ "network_two": [
997+ {
998+ "version": 4,
999+ "addr": "67.23.10.139",
1000+ },
1001+ {
1002+ "version": 6,
1003+ "addr": "::babe:67.23.10.139",
1004+ },
1005+ ],
1006+ },
1007+ "metadata": {
1008+ "Open": "Stack",
1009+ "Number": "1",
1010+ },
1011+ 'links': [
1012+ {
1013+ 'href': self.SERVER_HREF,
1014+ 'rel': 'self',
1015+ },
1016+ {
1017+ 'href': self.SERVER_BOOKMARK,
1018+ 'rel': 'bookmark',
1019+ },
1020+ ],
1021+ }
1022+ }
1023+
1024+ output = serializer.serialize(fixture, 'show')
1025+ actual = minidom.parseString(output.replace(" ", ""))
1026+
1027+ expected_server_href = self.SERVER_HREF
1028+ expected_server_bookmark = self.SERVER_BOOKMARK
1029+ expected_image_bookmark = self.IMAGE_BOOKMARK
1030+ expected_flavor_bookmark = self.FLAVOR_BOOKMARK
1031+ expected_now = self.TIMESTAMP
1032+ expected_uuid = FAKE_UUID
1033+ expected = minidom.parseString("""
1034+ <server id="1"
1035+ uuid="%(expected_uuid)s"
1036+ xmlns="http://docs.openstack.org/compute/api/v1.1"
1037+ xmlns:atom="http://www.w3.org/2005/Atom"
1038+ name="test_server"
1039+ updated="%(expected_now)s"
1040+ created="%(expected_now)s"
1041+ hostId="e4d909c290d0fb1ca068ffaddf22cbd0"
1042+ status="BUILD"
1043+ progress="0">
1044+ <atom:link href="%(expected_server_href)s" rel="self"/>
1045+ <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/>
1046+ <image id="5">
1047+ <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/>
1048+ </image>
1049+ <flavor id="1">
1050+ <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/>
1051+ </flavor>
1052+ <metadata>
1053+ <meta key="Open">
1054+ Stack
1055+ </meta>
1056+ <meta key="Number">
1057+ 1
1058+ </meta>
1059+ </metadata>
1060+ <addresses>
1061+ <network id="network_one">
1062+ <ip version="4" addr="67.23.10.138"/>
1063+ <ip version="6" addr="::babe:67.23.10.138"/>
1064+ </network>
1065+ <network id="network_two">
1066+ <ip version="4" addr="67.23.10.139"/>
1067+ <ip version="6" addr="::babe:67.23.10.139"/>
1068+ </network>
1069+ </addresses>
1070+ </server>
1071+ """.replace(" ", "") % (locals()))
1072+
1073+ self.assertEqual(expected.toxml(), actual.toxml())
1074+
1075+ def test_create(self):
1076+ serializer = servers.ServerXMLSerializer()
1077+
1078+ fixture = {
1079+ "server": {
1080+ "id": 1,
1081+ "uuid": FAKE_UUID,
1082+ 'created': self.TIMESTAMP,
1083+ 'updated': self.TIMESTAMP,
1084+ "progress": 0,
1085+ "name": "test_server",
1086+ "status": "BUILD",
1087+ "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0",
1088+ "adminPass": "test_password",
1089+ "image": {
1090+ "id": "5",
1091+ "links": [
1092+ {
1093+ "rel": "bookmark",
1094+ "href": self.IMAGE_BOOKMARK,
1095+ },
1096+ ],
1097+ },
1098+ "flavor": {
1099+ "id": "1",
1100+ "links": [
1101+ {
1102+ "rel": "bookmark",
1103+ "href": self.FLAVOR_BOOKMARK,
1104+ },
1105+ ],
1106+ },
1107+ "addresses": {
1108+ "network_one": [
1109+ {
1110+ "version": 4,
1111+ "addr": "67.23.10.138",
1112+ },
1113+ {
1114+ "version": 6,
1115+ "addr": "::babe:67.23.10.138",
1116+ },
1117+ ],
1118+ "network_two": [
1119+ {
1120+ "version": 4,
1121+ "addr": "67.23.10.139",
1122+ },
1123+ {
1124+ "version": 6,
1125+ "addr": "::babe:67.23.10.139",
1126+ },
1127+ ],
1128+ },
1129+ "metadata": {
1130+ "Open": "Stack",
1131+ "Number": "1",
1132+ },
1133+ 'links': [
1134+ {
1135+ 'href': self.SERVER_HREF,
1136+ 'rel': 'self',
1137+ },
1138+ {
1139+ 'href': self.SERVER_BOOKMARK,
1140+ 'rel': 'bookmark',
1141+ },
1142+ ],
1143+ }
1144+ }
1145+
1146+ output = serializer.serialize(fixture, 'create')
1147+ actual = minidom.parseString(output.replace(" ", ""))
1148+
1149+ expected_server_href = self.SERVER_HREF
1150+ expected_server_bookmark = self.SERVER_BOOKMARK
1151+ expected_image_bookmark = self.IMAGE_BOOKMARK
1152+ expected_flavor_bookmark = self.FLAVOR_BOOKMARK
1153+ expected_now = self.TIMESTAMP
1154+ expected_uuid = FAKE_UUID
1155+ expected = minidom.parseString("""
1156+ <server id="1"
1157+ uuid="%(expected_uuid)s"
1158+ xmlns="http://docs.openstack.org/compute/api/v1.1"
1159+ xmlns:atom="http://www.w3.org/2005/Atom"
1160+ name="test_server"
1161+ updated="%(expected_now)s"
1162+ created="%(expected_now)s"
1163+ hostId="e4d909c290d0fb1ca068ffaddf22cbd0"
1164+ status="BUILD"
1165+ adminPass="test_password"
1166+ progress="0">
1167+ <atom:link href="%(expected_server_href)s" rel="self"/>
1168+ <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/>
1169+ <image id="5">
1170+ <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/>
1171+ </image>
1172+ <flavor id="1">
1173+ <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/>
1174+ </flavor>
1175+ <metadata>
1176+ <meta key="Open">
1177+ Stack
1178+ </meta>
1179+ <meta key="Number">
1180+ 1
1181+ </meta>
1182+ </metadata>
1183+ <addresses>
1184+ <network id="network_one">
1185+ <ip version="4" addr="67.23.10.138"/>
1186+ <ip version="6" addr="::babe:67.23.10.138"/>
1187+ </network>
1188+ <network id="network_two">
1189+ <ip version="4" addr="67.23.10.139"/>
1190+ <ip version="6" addr="::babe:67.23.10.139"/>
1191+ </network>
1192+ </addresses>
1193+ </server>
1194+ """.replace(" ", "") % (locals()))
1195+
1196+ self.assertEqual(expected.toxml(), actual.toxml())
1197+
1198+ def test_index(self):
1199+ serializer = servers.ServerXMLSerializer()
1200+
1201+ expected_server_href = 'http://localhost/v1.1/servers/1'
1202+ expected_server_bookmark = 'http://localhost/servers/1'
1203+ expected_server_href_2 = 'http://localhost/v1.1/servers/2'
1204+ expected_server_bookmark_2 = 'http://localhost/servers/2'
1205+ fixture = {"servers": [
1206+ {
1207+ "id": 1,
1208+ "name": "test_server",
1209+ 'links': [
1210+ {
1211+ 'href': expected_server_href,
1212+ 'rel': 'self',
1213+ },
1214+ {
1215+ 'href': expected_server_bookmark,
1216+ 'rel': 'bookmark',
1217+ },
1218+ ],
1219+ },
1220+ {
1221+ "id": 2,
1222+ "name": "test_server_2",
1223+ 'links': [
1224+ {
1225+ 'href': expected_server_href_2,
1226+ 'rel': 'self',
1227+ },
1228+ {
1229+ 'href': expected_server_bookmark_2,
1230+ 'rel': 'bookmark',
1231+ },
1232+ ],
1233+ },
1234+ ]}
1235+
1236+ output = serializer.serialize(fixture, 'index')
1237+ actual = minidom.parseString(output.replace(" ", ""))
1238+
1239+ expected = minidom.parseString("""
1240+ <servers xmlns="http://docs.openstack.org/compute/api/v1.1"
1241+ xmlns:atom="http://www.w3.org/2005/Atom">
1242+ <server id="1" name="test_server">
1243+ <atom:link href="%(expected_server_href)s" rel="self"/>
1244+ <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/>
1245+ </server>
1246+ <server id="2" name="test_server_2">
1247+ <atom:link href="%(expected_server_href_2)s" rel="self"/>
1248+ <atom:link href="%(expected_server_bookmark_2)s" rel="bookmark"/>
1249+ </server>
1250+ </servers>
1251+ """.replace(" ", "") % (locals()))
1252+
1253+ self.assertEqual(expected.toxml(), actual.toxml())
1254+
1255+ def test_detail(self):
1256+ serializer = servers.ServerXMLSerializer()
1257+
1258+ expected_server_href = 'http://localhost/v1.1/servers/1'
1259+ expected_server_bookmark = 'http://localhost/servers/1'
1260+ expected_image_bookmark = self.IMAGE_BOOKMARK
1261+ expected_flavor_bookmark = self.FLAVOR_BOOKMARK
1262+ expected_now = self.TIMESTAMP
1263+ expected_uuid = FAKE_UUID
1264+
1265+ expected_server_href_2 = 'http://localhost/v1.1/servers/2'
1266+ expected_server_bookmark_2 = 'http://localhost/servers/2'
1267+ fixture = {"servers": [
1268+ {
1269+ "id": 1,
1270+ "uuid": FAKE_UUID,
1271+ 'created': self.TIMESTAMP,
1272+ 'updated': self.TIMESTAMP,
1273+ "progress": 0,
1274+ "name": "test_server",
1275+ "status": "BUILD",
1276+ "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
1277+ "image": {
1278+ "id": "5",
1279+ "links": [
1280+ {
1281+ "rel": "bookmark",
1282+ "href": expected_image_bookmark,
1283+ },
1284+ ],
1285+ },
1286+ "flavor": {
1287+ "id": "1",
1288+ "links": [
1289+ {
1290+ "rel": "bookmark",
1291+ "href": expected_flavor_bookmark,
1292+ },
1293+ ],
1294+ },
1295+ "addresses": {
1296+ "network_one": [
1297+ {
1298+ "version": 4,
1299+ "addr": "67.23.10.138",
1300+ },
1301+ {
1302+ "version": 6,
1303+ "addr": "::babe:67.23.10.138",
1304+ },
1305+ ],
1306+ },
1307+ "metadata": {
1308+ "Number": "1",
1309+ },
1310+ "links": [
1311+ {
1312+ "href": expected_server_href,
1313+ "rel": "self",
1314+ },
1315+ {
1316+ "href": expected_server_bookmark,
1317+ "rel": "bookmark",
1318+ },
1319+ ],
1320+ },
1321+ {
1322+ "id": 2,
1323+ "uuid": FAKE_UUID,
1324+ 'created': self.TIMESTAMP,
1325+ 'updated': self.TIMESTAMP,
1326+ "progress": 100,
1327+ "name": "test_server_2",
1328+ "status": "ACTIVE",
1329+ "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
1330+ "image": {
1331+ "id": "5",
1332+ "links": [
1333+ {
1334+ "rel": "bookmark",
1335+ "href": expected_image_bookmark,
1336+ },
1337+ ],
1338+ },
1339+ "flavor": {
1340+ "id": "1",
1341+ "links": [
1342+ {
1343+ "rel": "bookmark",
1344+ "href": expected_flavor_bookmark,
1345+ },
1346+ ],
1347+ },
1348+ "addresses": {
1349+ "network_one": [
1350+ {
1351+ "version": 4,
1352+ "addr": "67.23.10.138",
1353+ },
1354+ {
1355+ "version": 6,
1356+ "addr": "::babe:67.23.10.138",
1357+ },
1358+ ],
1359+ },
1360+ "metadata": {
1361+ "Number": "2",
1362+ },
1363+ "links": [
1364+ {
1365+ "href": expected_server_href_2,
1366+ "rel": "self",
1367+ },
1368+ {
1369+ "href": expected_server_bookmark_2,
1370+ "rel": "bookmark",
1371+ },
1372+ ],
1373+ },
1374+ ]}
1375+
1376+ output = serializer.serialize(fixture, 'detail')
1377+ actual = minidom.parseString(output.replace(" ", ""))
1378+
1379+ expected = minidom.parseString("""
1380+ <servers xmlns="http://docs.openstack.org/compute/api/v1.1"
1381+ xmlns:atom="http://www.w3.org/2005/Atom">
1382+ <server id="1"
1383+ uuid="%(expected_uuid)s"
1384+ name="test_server"
1385+ updated="%(expected_now)s"
1386+ created="%(expected_now)s"
1387+ hostId="e4d909c290d0fb1ca068ffaddf22cbd0"
1388+ status="BUILD"
1389+ progress="0">
1390+ <atom:link href="%(expected_server_href)s" rel="self"/>
1391+ <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/>
1392+ <image id="5">
1393+ <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/>
1394+ </image>
1395+ <flavor id="1">
1396+ <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/>
1397+ </flavor>
1398+ <metadata>
1399+ <meta key="Number">
1400+ 1
1401+ </meta>
1402+ </metadata>
1403+ <addresses>
1404+ <network id="network_one">
1405+ <ip version="4" addr="67.23.10.138"/>
1406+ <ip version="6" addr="::babe:67.23.10.138"/>
1407+ </network>
1408+ </addresses>
1409+ </server>
1410+ <server id="2"
1411+ uuid="%(expected_uuid)s"
1412+ name="test_server_2"
1413+ updated="%(expected_now)s"
1414+ created="%(expected_now)s"
1415+ hostId="e4d909c290d0fb1ca068ffaddf22cbd0"
1416+ status="ACTIVE"
1417+ progress="100">
1418+ <atom:link href="%(expected_server_href_2)s" rel="self"/>
1419+ <atom:link href="%(expected_server_bookmark_2)s" rel="bookmark"/>
1420+ <image id="5">
1421+ <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/>
1422+ </image>
1423+ <flavor id="1">
1424+ <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/>
1425+ </flavor>
1426+ <metadata>
1427+ <meta key="Number">
1428+ 2
1429+ </meta>
1430+ </metadata>
1431+ <addresses>
1432+ <network id="network_one">
1433+ <ip version="4" addr="67.23.10.138"/>
1434+ <ip version="6" addr="::babe:67.23.10.138"/>
1435+ </network>
1436+ </addresses>
1437+ </server>
1438+ </servers>
1439+ """.replace(" ", "") % (locals()))
1440+
1441+ self.assertEqual(expected.toxml(), actual.toxml())