Merge lp:~ethuleau/nova/lp838154 into lp:~hudson-openstack/nova/milestone-proposed

Proposed by Vish Ishaya
Status: Rejected
Rejected by: Vish Ishaya
Proposed branch: lp:~ethuleau/nova/lp838154
Merge into: lp:~hudson-openstack/nova/milestone-proposed
Diff against target: 8638 lines (+3773/-2343) (has conflicts)
66 files modified
MANIFEST.in (+1/-1)
nova/api/ec2/cloud.py (+1/-1)
nova/api/openstack/common.py (+43/-56)
nova/api/openstack/contrib/flavorextradata.py (+46/-0)
nova/api/openstack/flavors.py (+37/-36)
nova/api/openstack/image_metadata.py (+28/-30)
nova/api/openstack/images.py (+57/-78)
nova/api/openstack/ips.py (+28/-33)
nova/api/openstack/limits.py (+38/-40)
nova/api/openstack/schemas/v1.1/addresses.rng (+14/-0)
nova/api/openstack/schemas/v1.1/flavor.rng (+14/-0)
nova/api/openstack/schemas/v1.1/flavors.rng (+6/-0)
nova/api/openstack/schemas/v1.1/flavors_index.rng (+12/-0)
nova/api/openstack/schemas/v1.1/image.rng (+30/-0)
nova/api/openstack/schemas/v1.1/images.rng (+6/-0)
nova/api/openstack/schemas/v1.1/images_index.rng (+12/-0)
nova/api/openstack/schemas/v1.1/limits.rng (+28/-0)
nova/api/openstack/schemas/v1.1/metadata.rng (+9/-0)
nova/api/openstack/schemas/v1.1/server.rng (+3/-3)
nova/api/openstack/servers.py (+108/-36)
nova/api/openstack/versions.py (+91/-167)
nova/api/openstack/views/flavors.py (+3/-0)
nova/api/openstack/views/images.py (+10/-0)
nova/api/openstack/views/versions.py (+1/-1)
nova/api/openstack/wsgi.py (+44/-6)
nova/compute/api.py (+10/-1)
nova/compute/manager.py (+12/-1)
nova/image/fake.py (+1/-2)
nova/image/glance.py (+95/-23)
nova/image/s3.py (+1/-2)
nova/image/service.py (+0/-200)
nova/scheduler/abstract_scheduler.py (+1/-1)
nova/scheduler/base_scheduler.py (+38/-14)
nova/tests/api/openstack/common.py (+22/-0)
nova/tests/api/openstack/contrib/test_createserverext.py (+39/-0)
nova/tests/api/openstack/fakes.py (+40/-87)
nova/tests/api/openstack/test_api.py (+25/-0)
nova/tests/api/openstack/test_common.py (+115/-67)
nova/tests/api/openstack/test_extensions.py (+1/-0)
nova/tests/api/openstack/test_flavors.py (+158/-101)
nova/tests/api/openstack/test_image_metadata.py (+65/-102)
nova/tests/api/openstack/test_images.py (+542/-571)
nova/tests/api/openstack/test_limits.py (+63/-45)
nova/tests/api/openstack/test_servers.py (+207/-32)
nova/tests/api/openstack/test_versions.py (+258/-360)
nova/tests/api/openstack/test_wsgi.py (+34/-23)
nova/tests/fake_network.py (+194/-0)
nova/tests/glance/stubs.py (+73/-1)
nova/tests/image/test_glance.py (+399/-2)
nova/tests/integrated/test_xml.py (+6/-6)
nova/tests/test_compute.py (+14/-5)
nova/tests/test_direct.py (+1/-1)
nova/tests/test_libvirt.py (+137/-147)
nova/tests/test_network.py (+32/-43)
nova/tests/test_virt_drivers.py (+493/-0)
nova/tests/test_vmwareapi.py (+2/-1)
nova/version.py (+1/-1)
nova/virt/driver.py (+2/-1)
nova/virt/fake.py (+1/-1)
nova/virt/hyperv.py (+1/-1)
nova/virt/libvirt/connection.py (+7/-6)
nova/virt/libvirt/firewall.py (+2/-2)
nova/virt/vmwareapi_conn.py (+1/-1)
nova/virt/xenapi/vmops.py (+7/-2)
nova/virt/xenapi_conn.py (+2/-2)
tools/pip-requires (+1/-0)
Text conflict in nova/api/openstack/common.py
Text conflict in nova/api/openstack/servers.py
Text conflict in nova/compute/api.py
Text conflict in nova/compute/manager.py
Text conflict in nova/image/glance.py
Text conflict in nova/scheduler/base_scheduler.py
Text conflict in nova/tests/api/openstack/contrib/test_createserverext.py
Text conflict in nova/tests/api/openstack/test_images.py
Text conflict in nova/tests/api/openstack/test_servers.py
Text conflict in nova/tests/glance/stubs.py
Text conflict in nova/tests/image/test_glance.py
Text conflict in nova/tests/test_libvirt.py
Text conflict in nova/tests/test_virt_drivers.py
To merge this branch: bzr merge lp:~ethuleau/nova/lp838154
Reviewer Review Type Date Requested Status
OpenStack release team Pending
Review via email: mp+75845@code.launchpad.net

Description of the change

Fixes libvirt rescue to use the same strategy as xen. Use a new copy of the base image as the rescue image. It leaves the original rescue image flags in, so a hand picked rescue image can still be used if desired.

To post a comment you must log in.
Revision history for this message
Thierry Carrez (ttx) wrote :

Change description sounds unrelated

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

this looks like an error.

Unmerged revisions

1544. By Édouard Thuleau

Merged trunk.

1543. By Édouard Thuleau

Authorize to start a LXC instance withour, key, network file to inject or metadata.

1542. By Thierry Carrez

Open Essex (switch version to 2012.1)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'MANIFEST.in'
2--- MANIFEST.in 2011-08-09 13:25:52 +0000
3+++ MANIFEST.in 2011-09-17 02:16:44 +0000
4@@ -37,7 +37,7 @@
5 include nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml
6 include nova/tests/bundle/1mb.part.0
7 include nova/tests/bundle/1mb.part.1
8-include nova/tests/public_key/*
9+include nova/tests/api/ec2/public_key/*
10 include nova/tests/db/nova.austin.sqlite
11 include plugins/xenapi/README
12 include plugins/xenapi/etc/xapi.d/plugins/objectstore
13
14=== modified file 'nova/api/ec2/cloud.py'
15--- nova/api/ec2/cloud.py 2011-09-15 20:40:49 +0000
16+++ nova/api/ec2/cloud.py 2011-09-17 02:16:44 +0000
17@@ -1489,7 +1489,7 @@
18 return image
19
20 def _format_image(self, image):
21- """Convert from format defined by BaseImageService to S3 format."""
22+ """Convert from format defined by GlanceImageService to S3 format."""
23 i = {}
24 image_type = self._image_type(image.get('container_format'))
25 ec2_id = self.image_ec2_id(image.get('id'), image_type)
26
27=== modified file 'nova/api/openstack/common.py'
28--- nova/api/openstack/common.py 2011-08-25 21:27:10 +0000
29+++ nova/api/openstack/common.py 2011-09-17 02:16:44 +0000
30@@ -16,6 +16,7 @@
31 # under the License.
32
33 import functools
34+from lxml import etree
35 import re
36 import urlparse
37 from xml.dom import minidom
38@@ -27,8 +28,14 @@
39 from nova import log as logging
40 from nova import quota
41 from nova.api.openstack import wsgi
42-from nova.compute import vm_states
43-from nova.compute import task_states
44+<<<<<<< TREE
45+from nova.compute import vm_states
46+from nova.compute import task_states
47+=======
48+from nova.api.openstack import xmlutil
49+from nova.compute import vm_states
50+from nova.compute import task_states
51+>>>>>>> MERGE-SOURCE
52
53
54 LOG = logging.getLogger('nova.api.openstack.common')
55@@ -185,30 +192,16 @@
56
57
58 def get_id_from_href(href):
59- """Return the id portion of a url as an int.
60+ """Return the id or uuid portion of a url.
61
62 Given: 'http://www.foo.com/bar/123?q=4'
63- Returns: 123
64+ Returns: '123'
65
66- In order to support local hrefs, the href argument can be just an id:
67- Given: '123'
68- Returns: 123
69+ Given: 'http://www.foo.com/bar/abc123?q=4'
70+ Returns: 'abc123'
71
72 """
73- LOG.debug(_("Attempting to treat %(href)s as an integer ID.") % locals())
74-
75- try:
76- return int(href)
77- except ValueError:
78- pass
79-
80- LOG.debug(_("Attempting to treat %(href)s as a URL.") % locals())
81-
82- try:
83- return int(urlparse.urlsplit(href).path.split('/')[-1])
84- except ValueError as error:
85- LOG.debug(_("Failed to parse ID from %(href)s: %(error)s") % locals())
86- raise
87+ return urlparse.urlsplit("%s" % href).path.split('/')[-1]
88
89
90 def remove_version_from_href(href):
91@@ -308,54 +301,48 @@
92
93
94 class MetadataXMLSerializer(wsgi.XMLDictSerializer):
95+
96+ NSMAP = {None: xmlutil.XMLNS_V11}
97+
98 def __init__(self, xmlns=wsgi.XMLNS_V11):
99 super(MetadataXMLSerializer, self).__init__(xmlns=xmlns)
100
101- def _meta_item_to_xml(self, doc, key, value):
102- node = doc.createElement('meta')
103- doc.appendChild(node)
104- node.setAttribute('key', '%s' % key)
105- text = doc.createTextNode('%s' % value)
106- node.appendChild(text)
107- return node
108-
109- def meta_list_to_xml(self, xml_doc, meta_items):
110- container_node = xml_doc.createElement('metadata')
111- for (key, value) in meta_items:
112- item_node = self._meta_item_to_xml(xml_doc, key, value)
113- container_node.appendChild(item_node)
114- return container_node
115-
116- def _meta_list_to_xml_string(self, metadata_dict):
117- xml_doc = minidom.Document()
118- items = metadata_dict['metadata'].items()
119- container_node = self.meta_list_to_xml(xml_doc, items)
120- xml_doc.appendChild(container_node)
121- self._add_xmlns(container_node)
122- return xml_doc.toxml('UTF-8')
123+ def populate_metadata(self, metadata_elem, meta_dict):
124+ for (key, value) in meta_dict.items():
125+ elem = etree.SubElement(metadata_elem, 'meta')
126+ elem.set('key', str(key))
127+ elem.text = value
128+
129+ def _populate_meta_item(self, meta_elem, meta_item_dict):
130+ """Populate a meta xml element from a dict."""
131+ (key, value) = meta_item_dict.items()[0]
132+ meta_elem.set('key', str(key))
133+ meta_elem.text = value
134
135 def index(self, metadata_dict):
136- return self._meta_list_to_xml_string(metadata_dict)
137+ metadata = etree.Element('metadata', nsmap=self.NSMAP)
138+ self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
139+ return self._to_xml(metadata)
140
141 def create(self, metadata_dict):
142- return self._meta_list_to_xml_string(metadata_dict)
143+ metadata = etree.Element('metadata', nsmap=self.NSMAP)
144+ self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
145+ return self._to_xml(metadata)
146
147 def update_all(self, metadata_dict):
148- return self._meta_list_to_xml_string(metadata_dict)
149-
150- def _meta_item_to_xml_string(self, meta_item_dict):
151- xml_doc = minidom.Document()
152- item_key, item_value = meta_item_dict.items()[0]
153- item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
154- xml_doc.appendChild(item_node)
155- self._add_xmlns(item_node)
156- return xml_doc.toxml('UTF-8')
157+ metadata = etree.Element('metadata', nsmap=self.NSMAP)
158+ self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
159+ return self._to_xml(metadata)
160
161 def show(self, meta_item_dict):
162- return self._meta_item_to_xml_string(meta_item_dict['meta'])
163+ meta = etree.Element('meta', nsmap=self.NSMAP)
164+ self._populate_meta_item(meta, meta_item_dict['meta'])
165+ return self._to_xml(meta)
166
167 def update(self, meta_item_dict):
168- return self._meta_item_to_xml_string(meta_item_dict['meta'])
169+ meta = etree.Element('meta', nsmap=self.NSMAP)
170+ self._populate_meta_item(meta, meta_item_dict['meta'])
171+ return self._to_xml(meta)
172
173 def default(self, *args, **kwargs):
174 return ''
175
176=== added file 'nova/api/openstack/contrib/flavorextradata.py'
177--- nova/api/openstack/contrib/flavorextradata.py 1970-01-01 00:00:00 +0000
178+++ nova/api/openstack/contrib/flavorextradata.py 2011-09-17 02:16:44 +0000
179@@ -0,0 +1,46 @@
180+# Copyright 2011 Canonical Ltd.
181+# All Rights Reserved.
182+#
183+# Licensed under the Apache License, Version 2.0 (the "License"); you may
184+# not use this file except in compliance with the License. You may obtain
185+# a copy of the License at
186+#
187+# http://www.apache.org/licenses/LICENSE-2.0
188+#
189+# Unless required by applicable law or agreed to in writing, software
190+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
191+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
192+# License for the specific language governing permissions and limitations
193+# under the License.
194+
195+"""
196+The Flavor extra data extension
197+Openstack API version 1.1 lists "name", "ram", "disk", "vcpus" as flavor
198+attributes. This extension adds to that list:
199+ rxtx_cap
200+ rxtx_quota
201+ swap
202+"""
203+
204+from nova.api.openstack import extensions
205+
206+
207+class Flavorextradata(extensions.ExtensionDescriptor):
208+ """The Flavor extra data extension for the OpenStack API."""
209+
210+ def get_name(self):
211+ return "FlavorExtraData"
212+
213+ def get_alias(self):
214+ return "os-flavor-extra-data"
215+
216+ def get_description(self):
217+ return "Provide additional data for flavors"
218+
219+ def get_namespace(self):
220+ return "http://docs.openstack.org/ext/flavor_extra_data/api/v1.1"
221+
222+ def get_updated(self):
223+ return "2011-09-14T00:00:00+00:00"
224+
225+# vim: tabstop=4 shiftwidth=4 softtabstop=4
226
227=== modified file 'nova/api/openstack/flavors.py'
228--- nova/api/openstack/flavors.py 2011-08-10 06:01:03 +0000
229+++ nova/api/openstack/flavors.py 2011-09-17 02:16:44 +0000
230@@ -16,12 +16,13 @@
231 # under the License.
232
233 import webob
234-import xml.dom.minidom as minidom
235+from lxml import etree
236
237 from nova import db
238 from nova import exception
239 from nova.api.openstack import views
240 from nova.api.openstack import wsgi
241+from nova.api.openstack import xmlutil
242
243
244 class Controller(object):
245@@ -78,48 +79,48 @@
246
247 class FlavorXMLSerializer(wsgi.XMLDictSerializer):
248
249+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
250+
251 def __init__(self):
252 super(FlavorXMLSerializer, self).__init__(xmlns=wsgi.XMLNS_V11)
253
254- def _flavor_to_xml(self, xml_doc, flavor, detailed):
255- flavor_node = xml_doc.createElement('flavor')
256- flavor_node.setAttribute('id', str(flavor['id']))
257- flavor_node.setAttribute('name', flavor['name'])
258+ def _populate_flavor(self, flavor_elem, flavor_dict, detailed=False):
259+ """Populate a flavor xml element from a dict."""
260
261+ flavor_elem.set('name', flavor_dict['name'])
262+ flavor_elem.set('id', str(flavor_dict['id']))
263 if detailed:
264- flavor_node.setAttribute('ram', str(flavor['ram']))
265- flavor_node.setAttribute('disk', str(flavor['disk']))
266-
267- link_nodes = self._create_link_nodes(xml_doc, flavor['links'])
268- for link_node in link_nodes:
269- flavor_node.appendChild(link_node)
270- return flavor_node
271-
272- def _flavors_list_to_xml(self, xml_doc, flavors, detailed):
273- container_node = xml_doc.createElement('flavors')
274-
275- for flavor in flavors:
276- item_node = self._flavor_to_xml(xml_doc, flavor, detailed)
277- container_node.appendChild(item_node)
278- return container_node
279+ flavor_elem.set('ram', str(flavor_dict['ram']))
280+ flavor_elem.set('disk', str(flavor_dict['disk']))
281+
282+ for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
283+ flavor_elem.set(attr, str(flavor_dict.get(attr, "")))
284+
285+ for link in flavor_dict.get('links', []):
286+ elem = etree.SubElement(flavor_elem,
287+ '{%s}link' % xmlutil.XMLNS_ATOM)
288+ elem.set('rel', link['rel'])
289+ elem.set('href', link['href'])
290+ return flavor_elem
291
292 def show(self, flavor_container):
293- xml_doc = minidom.Document()
294- flavor = flavor_container['flavor']
295- node = self._flavor_to_xml(xml_doc, flavor, True)
296- return self.to_xml_string(node, True)
297-
298- def detail(self, flavors_container):
299- xml_doc = minidom.Document()
300- flavors = flavors_container['flavors']
301- node = self._flavors_list_to_xml(xml_doc, flavors, True)
302- return self.to_xml_string(node, True)
303-
304- def index(self, flavors_container):
305- xml_doc = minidom.Document()
306- flavors = flavors_container['flavors']
307- node = self._flavors_list_to_xml(xml_doc, flavors, False)
308- return self.to_xml_string(node, True)
309+ flavor = etree.Element('flavor', nsmap=self.NSMAP)
310+ self._populate_flavor(flavor, flavor_container['flavor'], True)
311+ return self._to_xml(flavor)
312+
313+ def detail(self, flavors_dict):
314+ flavors = etree.Element('flavors', nsmap=self.NSMAP)
315+ for flavor_dict in flavors_dict['flavors']:
316+ flavor = etree.SubElement(flavors, 'flavor')
317+ self._populate_flavor(flavor, flavor_dict, True)
318+ return self._to_xml(flavors)
319+
320+ def index(self, flavors_dict):
321+ flavors = etree.Element('flavors', nsmap=self.NSMAP)
322+ for flavor_dict in flavors_dict['flavors']:
323+ flavor = etree.SubElement(flavors, 'flavor')
324+ self._populate_flavor(flavor, flavor_dict, False)
325+ return self._to_xml(flavors)
326
327
328 def create_resource(version='1.0'):
329
330=== modified file 'nova/api/openstack/image_metadata.py'
331--- nova/api/openstack/image_metadata.py 2011-08-06 00:05:33 +0000
332+++ nova/api/openstack/image_metadata.py 2011-09-17 02:16:44 +0000
333@@ -17,6 +17,7 @@
334
335 from webob import exc
336
337+from nova import exception
338 from nova import flags
339 from nova import image
340 from nova import utils
341@@ -33,21 +34,22 @@
342 def __init__(self):
343 self.image_service = image.get_default_image_service()
344
345- def _get_metadata(self, context, image_id, image=None):
346- if not image:
347- image = self.image_service.show(context, image_id)
348- metadata = image.get('properties', {})
349- return metadata
350+ def _get_image(self, context, image_id):
351+ try:
352+ return self.image_service.show(context, image_id)
353+ except exception.NotFound:
354+ msg = _("Image not found.")
355+ raise exc.HTTPNotFound(explanation=msg)
356
357 def index(self, req, image_id):
358 """Returns the list of metadata for a given instance"""
359 context = req.environ['nova.context']
360- metadata = self._get_metadata(context, image_id)
361+ metadata = self._get_image(context, image_id)['properties']
362 return dict(metadata=metadata)
363
364 def show(self, req, image_id, id):
365 context = req.environ['nova.context']
366- metadata = self._get_metadata(context, image_id)
367+ metadata = self._get_image(context, image_id)['properties']
368 if id in metadata:
369 return {'meta': {id: metadata[id]}}
370 else:
371@@ -55,15 +57,13 @@
372
373 def create(self, req, image_id, body):
374 context = req.environ['nova.context']
375- img = self.image_service.show(context, image_id)
376- metadata = self._get_metadata(context, image_id, img)
377+ image = self._get_image(context, image_id)
378 if 'metadata' in body:
379 for key, value in body['metadata'].iteritems():
380- metadata[key] = value
381- common.check_img_metadata_quota_limit(context, metadata)
382- img['properties'] = metadata
383- self.image_service.update(context, image_id, img, None)
384- return dict(metadata=metadata)
385+ image['properties'][key] = value
386+ common.check_img_metadata_quota_limit(context, image['properties'])
387+ self.image_service.update(context, image_id, image, None)
388+ return dict(metadata=image['properties'])
389
390 def update(self, req, image_id, id, body):
391 context = req.environ['nova.context']
392@@ -80,32 +80,30 @@
393 if len(meta) > 1:
394 expl = _('Request body contains too many items')
395 raise exc.HTTPBadRequest(explanation=expl)
396- img = self.image_service.show(context, image_id)
397- metadata = self._get_metadata(context, image_id, img)
398- metadata[id] = meta[id]
399- common.check_img_metadata_quota_limit(context, metadata)
400- img['properties'] = metadata
401- self.image_service.update(context, image_id, img, None)
402+
403+ image = self._get_image(context, image_id)
404+ image['properties'][id] = meta[id]
405+ common.check_img_metadata_quota_limit(context, image['properties'])
406+ self.image_service.update(context, image_id, image, None)
407 return dict(meta=meta)
408
409 def update_all(self, req, image_id, body):
410 context = req.environ['nova.context']
411- img = self.image_service.show(context, image_id)
412+ image = self._get_image(context, image_id)
413 metadata = body.get('metadata', {})
414 common.check_img_metadata_quota_limit(context, metadata)
415- img['properties'] = metadata
416- self.image_service.update(context, image_id, img, None)
417+ image['properties'] = metadata
418+ self.image_service.update(context, image_id, image, None)
419 return dict(metadata=metadata)
420
421 def delete(self, req, image_id, id):
422 context = req.environ['nova.context']
423- img = self.image_service.show(context, image_id)
424- metadata = self._get_metadata(context, image_id)
425- if not id in metadata:
426- raise exc.HTTPNotFound()
427- metadata.pop(id)
428- img['properties'] = metadata
429- self.image_service.update(context, image_id, img, None)
430+ image = self._get_image(context, image_id)
431+ if not id in image['properties']:
432+ msg = _("Invalid metadata key")
433+ raise exc.HTTPNotFound(explanation=msg)
434+ image['properties'].pop(id)
435+ self.image_service.update(context, image_id, image, None)
436
437
438 def create_resource():
439
440=== modified file 'nova/api/openstack/images.py'
441--- nova/api/openstack/images.py 2011-08-10 06:01:03 +0000
442+++ nova/api/openstack/images.py 2011-09-17 02:16:44 +0000
443@@ -16,8 +16,8 @@
444 import urlparse
445 import os.path
446
447+from lxml import etree
448 import webob.exc
449-from xml.dom import minidom
450
451 from nova import compute
452 from nova import exception
453@@ -29,6 +29,7 @@
454 from nova.api.openstack import servers
455 from nova.api.openstack.views import images as images_view
456 from nova.api.openstack import wsgi
457+from nova.api.openstack import xmlutil
458
459
460 LOG = log.getLogger('nova.api.openstack.images')
461@@ -50,7 +51,7 @@
462 """Initialize new `ImageController`.
463
464 :param compute_service: `nova.compute.api:API`
465- :param image_service: `nova.image.service:BaseImageService`
466+ :param image_service: `nova.image.glance:GlancemageService`
467
468 """
469 self._compute_service = compute_service or compute.API()
470@@ -206,93 +207,71 @@
471
472 class ImageXMLSerializer(wsgi.XMLDictSerializer):
473
474- xmlns = wsgi.XMLNS_V11
475+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
476
477 def __init__(self):
478 self.metadata_serializer = common.MetadataXMLSerializer()
479
480- def _image_to_xml(self, xml_doc, image):
481- image_node = xml_doc.createElement('image')
482- image_node.setAttribute('id', str(image['id']))
483- image_node.setAttribute('name', image['name'])
484- link_nodes = self._create_link_nodes(xml_doc,
485- image['links'])
486- for link_node in link_nodes:
487- image_node.appendChild(link_node)
488- return image_node
489-
490- def _image_to_xml_detailed(self, xml_doc, image):
491- image_node = xml_doc.createElement('image')
492- self._add_image_attributes(image_node, image)
493-
494- if 'server' in image:
495- server_node = self._create_server_node(xml_doc, image['server'])
496- image_node.appendChild(server_node)
497-
498- metadata = image.get('metadata', {}).items()
499- if len(metadata) > 0:
500- metadata_node = self._create_metadata_node(xml_doc, metadata)
501- image_node.appendChild(metadata_node)
502-
503- link_nodes = self._create_link_nodes(xml_doc,
504- image['links'])
505- for link_node in link_nodes:
506- image_node.appendChild(link_node)
507-
508- return image_node
509-
510- def _add_image_attributes(self, node, image):
511- node.setAttribute('id', str(image['id']))
512- node.setAttribute('name', image['name'])
513- node.setAttribute('created', image['created'])
514- node.setAttribute('updated', image['updated'])
515- node.setAttribute('status', image['status'])
516- if 'progress' in image:
517- node.setAttribute('progress', str(image['progress']))
518-
519- def _create_metadata_node(self, xml_doc, metadata):
520- return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata)
521-
522- def _create_server_node(self, xml_doc, server):
523- server_node = xml_doc.createElement('server')
524- server_node.setAttribute('id', str(server['id']))
525- link_nodes = self._create_link_nodes(xml_doc,
526- server['links'])
527- for link_node in link_nodes:
528- server_node.appendChild(link_node)
529- return server_node
530-
531- def _image_list_to_xml(self, xml_doc, images, detailed):
532- container_node = xml_doc.createElement('images')
533+ def _create_metadata_node(self, metadata_dict):
534+ metadata_elem = etree.Element('metadata', nsmap=self.NSMAP)
535+ self.metadata_serializer.populate_metadata(metadata_elem,
536+ metadata_dict)
537+ return metadata_elem
538+
539+ def _create_server_node(self, server_dict):
540+ server_elem = etree.Element('server', nsmap=self.NSMAP)
541+ server_elem.set('id', str(server_dict['id']))
542+ for link in server_dict.get('links', []):
543+ elem = etree.SubElement(server_elem,
544+ '{%s}link' % xmlutil.XMLNS_ATOM)
545+ elem.set('rel', link['rel'])
546+ elem.set('href', link['href'])
547+ return server_elem
548+
549+ def _populate_image(self, image_elem, image_dict, detailed=False):
550+ """Populate an image xml element from a dict."""
551+
552+ image_elem.set('name', image_dict['name'])
553+ image_elem.set('id', str(image_dict['id']))
554 if detailed:
555- image_to_xml = self._image_to_xml_detailed
556- else:
557- image_to_xml = self._image_to_xml
558-
559- for image in images:
560- item_node = image_to_xml(xml_doc, image)
561- container_node.appendChild(item_node)
562- return container_node
563+ image_elem.set('updated', str(image_dict['updated']))
564+ image_elem.set('created', str(image_dict['created']))
565+ image_elem.set('status', str(image_dict['status']))
566+ if 'progress' in image_dict:
567+ image_elem.set('progress', str(image_dict['progress']))
568+ if 'server' in image_dict:
569+ server_elem = self._create_server_node(image_dict['server'])
570+ image_elem.append(server_elem)
571+
572+ meta_elem = self._create_metadata_node(
573+ image_dict.get('metadata', {}))
574+ image_elem.append(meta_elem)
575+
576+ for link in image_dict.get('links', []):
577+ elem = etree.SubElement(image_elem,
578+ '{%s}link' % xmlutil.XMLNS_ATOM)
579+ elem.set('rel', link['rel'])
580+ elem.set('href', link['href'])
581+ return image_elem
582
583 def index(self, images_dict):
584- xml_doc = minidom.Document()
585- node = self._image_list_to_xml(xml_doc,
586- images_dict['images'],
587- detailed=False)
588- return self.to_xml_string(node, True)
589+ images = etree.Element('images', nsmap=self.NSMAP)
590+ for image_dict in images_dict['images']:
591+ image = etree.SubElement(images, 'image')
592+ self._populate_image(image, image_dict, False)
593+ return self._to_xml(images)
594
595 def detail(self, images_dict):
596- xml_doc = minidom.Document()
597- node = self._image_list_to_xml(xml_doc,
598- images_dict['images'],
599- detailed=True)
600- return self.to_xml_string(node, True)
601+ images = etree.Element('images', nsmap=self.NSMAP)
602+ for image_dict in images_dict['images']:
603+ image = etree.SubElement(images, 'image')
604+ self._populate_image(image, image_dict, True)
605+ return self._to_xml(images)
606
607 def show(self, image_dict):
608- xml_doc = minidom.Document()
609- node = self._image_to_xml_detailed(xml_doc,
610- image_dict['image'])
611- return self.to_xml_string(node, True)
612+ image = etree.Element('image', nsmap=self.NSMAP)
613+ self._populate_image(image, image_dict['image'], True)
614+ return self._to_xml(image)
615
616
617 def create_resource(version='1.0'):
618
619=== modified file 'nova/api/openstack/ips.py'
620--- nova/api/openstack/ips.py 2011-07-25 21:00:19 +0000
621+++ nova/api/openstack/ips.py 2011-09-17 02:16:44 +0000
622@@ -15,14 +15,15 @@
623 # License for the specific language governing permissions and limitations
624 # under the License.
625
626+from lxml import etree
627 import time
628-from xml.dom import minidom
629
630 from webob import exc
631
632 import nova
633 import nova.api.openstack.views.addresses
634 from nova.api.openstack import wsgi
635+from nova.api.openstack import xmlutil
636 from nova import db
637
638
639@@ -102,42 +103,36 @@
640
641
642 class IPXMLSerializer(wsgi.XMLDictSerializer):
643+
644+ NSMAP = {None: xmlutil.XMLNS_V11}
645+
646 def __init__(self, xmlns=wsgi.XMLNS_V11):
647 super(IPXMLSerializer, self).__init__(xmlns=xmlns)
648
649- def _ip_to_xml(self, xml_doc, ip_dict):
650- ip_node = xml_doc.createElement('ip')
651- ip_node.setAttribute('addr', ip_dict['addr'])
652- ip_node.setAttribute('version', str(ip_dict['version']))
653- return ip_node
654-
655- def _network_to_xml(self, xml_doc, network_id, ip_dicts):
656- network_node = xml_doc.createElement('network')
657- network_node.setAttribute('id', network_id)
658-
659+ def populate_addresses_node(self, addresses_elem, addresses_dict):
660+ for (network_id, ip_dicts) in addresses_dict.items():
661+ network_elem = self._create_network_node(network_id, ip_dicts)
662+ addresses_elem.append(network_elem)
663+
664+ def _create_network_node(self, network_id, ip_dicts):
665+ network_elem = etree.Element('network', nsmap=self.NSMAP)
666+ network_elem.set('id', str(network_id))
667 for ip_dict in ip_dicts:
668- ip_node = self._ip_to_xml(xml_doc, ip_dict)
669- network_node.appendChild(ip_node)
670-
671- return network_node
672-
673- def networks_to_xml(self, xml_doc, networks_container):
674- addresses_node = xml_doc.createElement('addresses')
675- for (network_id, ip_dicts) in networks_container.items():
676- network_node = self._network_to_xml(xml_doc, network_id, ip_dicts)
677- addresses_node.appendChild(network_node)
678- return addresses_node
679-
680- def show(self, network_container):
681- (network_id, ip_dicts) = network_container.items()[0]
682- xml_doc = minidom.Document()
683- node = self._network_to_xml(xml_doc, network_id, ip_dicts)
684- return self.to_xml_string(node, False)
685-
686- def index(self, addresses_container):
687- xml_doc = minidom.Document()
688- node = self.networks_to_xml(xml_doc, addresses_container['addresses'])
689- return self.to_xml_string(node, False)
690+ ip_elem = etree.SubElement(network_elem, 'ip')
691+ ip_elem.set('version', str(ip_dict['version']))
692+ ip_elem.set('addr', ip_dict['addr'])
693+ return network_elem
694+
695+ def show(self, network_dict):
696+ (network_id, ip_dicts) = network_dict.items()[0]
697+ network = self._create_network_node(network_id, ip_dicts)
698+ return self._to_xml(network)
699+
700+ def index(self, addresses_dict):
701+ addresses = etree.Element('addresses', nsmap=self.NSMAP)
702+ self.populate_addresses_node(addresses,
703+ addresses_dict.get('addresses', {}))
704+ return self._to_xml(addresses)
705
706
707 def create_resource(version):
708
709=== modified file 'nova/api/openstack/limits.py'
710--- nova/api/openstack/limits.py 2011-07-21 13:43:25 +0000
711+++ nova/api/openstack/limits.py 2011-09-17 02:16:44 +0000
712@@ -20,12 +20,12 @@
713 import copy
714 import httplib
715 import json
716+from lxml import etree
717 import math
718 import re
719 import time
720 import urllib
721 import webob.exc
722-from xml.dom import minidom
723
724 from collections import defaultdict
725
726@@ -38,6 +38,7 @@
727 from nova.api.openstack import faults
728 from nova.api.openstack.views import limits as limits_views
729 from nova.api.openstack import wsgi
730+from nova.api.openstack import xmlutil
731
732
733 # Convenience constants for the limits dictionary passed to Limiter().
734@@ -81,52 +82,49 @@
735
736 xmlns = wsgi.XMLNS_V11
737
738+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
739+
740 def __init__(self):
741 pass
742
743- def _create_rates_node(self, xml_doc, rates):
744- rates_node = xml_doc.createElement('rates')
745+ def _create_rates_node(self, rates):
746+ rates_elem = etree.Element('rates', nsmap=self.NSMAP)
747 for rate in rates:
748- rate_node = xml_doc.createElement('rate')
749- rate_node.setAttribute('uri', rate['uri'])
750- rate_node.setAttribute('regex', rate['regex'])
751-
752+ rate_node = etree.SubElement(rates_elem, 'rate')
753+ rate_node.set('uri', rate['uri'])
754+ rate_node.set('regex', rate['regex'])
755 for limit in rate['limit']:
756- limit_node = xml_doc.createElement('limit')
757- limit_node.setAttribute('value', str(limit['value']))
758- limit_node.setAttribute('verb', limit['verb'])
759- limit_node.setAttribute('remaining', str(limit['remaining']))
760- limit_node.setAttribute('unit', limit['unit'])
761- limit_node.setAttribute('next-available',
762- str(limit['next-available']))
763- rate_node.appendChild(limit_node)
764-
765- rates_node.appendChild(rate_node)
766- return rates_node
767-
768- def _create_absolute_node(self, xml_doc, absolutes):
769- absolute_node = xml_doc.createElement('absolute')
770- for key, value in absolutes.iteritems():
771- limit_node = xml_doc.createElement('limit')
772- limit_node.setAttribute('name', key)
773- limit_node.setAttribute('value', str(value))
774- absolute_node.appendChild(limit_node)
775- return absolute_node
776-
777- def _limits_to_xml(self, xml_doc, limits):
778- limits_node = xml_doc.createElement('limits')
779- rates_node = self._create_rates_node(xml_doc, limits['rate'])
780- limits_node.appendChild(rates_node)
781-
782- absolute_node = self._create_absolute_node(xml_doc, limits['absolute'])
783- limits_node.appendChild(absolute_node)
784-
785- return limits_node
786+ limit_elem = etree.SubElement(rate_node, 'limit')
787+ limit_elem.set('value', str(limit['value']))
788+ limit_elem.set('verb', str(limit['verb']))
789+ limit_elem.set('remaining', str(limit['remaining']))
790+ limit_elem.set('unit', str(limit['unit']))
791+ limit_elem.set('next-available', str(limit['next-available']))
792+ return rates_elem
793+
794+ def _create_absolute_node(self, absolute_dict):
795+ absolute_elem = etree.Element('absolute', nsmap=self.NSMAP)
796+ for key, value in absolute_dict.items():
797+ limit_elem = etree.SubElement(absolute_elem, 'limit')
798+ limit_elem.set('name', str(key))
799+ limit_elem.set('value', str(value))
800+ return absolute_elem
801+
802+ def _populate_limits(self, limits_elem, limits_dict):
803+ """Populate a limits xml element from a dict."""
804+
805+ rates_elem = self._create_rates_node(
806+ limits_dict.get('rate', []))
807+ limits_elem.append(rates_elem)
808+
809+ absolutes_elem = self._create_absolute_node(
810+ limits_dict.get('absolute', {}))
811+ limits_elem.append(absolutes_elem)
812
813 def index(self, limits_dict):
814- xml_doc = minidom.Document()
815- node = self._limits_to_xml(xml_doc, limits_dict['limits'])
816- return self.to_xml_string(node, False)
817+ limits = etree.Element('limits', nsmap=self.NSMAP)
818+ self._populate_limits(limits, limits_dict['limits'])
819+ return self._to_xml(limits)
820
821
822 def create_resource(version='1.0'):
823
824=== added file 'nova/api/openstack/schemas/v1.1/addresses.rng'
825--- nova/api/openstack/schemas/v1.1/addresses.rng 1970-01-01 00:00:00 +0000
826+++ nova/api/openstack/schemas/v1.1/addresses.rng 2011-09-17 02:16:44 +0000
827@@ -0,0 +1,14 @@
828+<element name="addresses" ns="http://docs.openstack.org/compute/api/v1.1"
829+ xmlns="http://relaxng.org/ns/structure/1.0">
830+ <zeroOrMore>
831+ <element name="network">
832+ <attribute name="id"> <text/> </attribute>
833+ <zeroOrMore>
834+ <element name="ip">
835+ <attribute name="version"> <text/> </attribute>
836+ <attribute name="addr"> <text/> </attribute>
837+ </element>
838+ </zeroOrMore>
839+ </element>
840+ </zeroOrMore>
841+</element>
842
843=== added file 'nova/api/openstack/schemas/v1.1/flavor.rng'
844--- nova/api/openstack/schemas/v1.1/flavor.rng 1970-01-01 00:00:00 +0000
845+++ nova/api/openstack/schemas/v1.1/flavor.rng 2011-09-17 02:16:44 +0000
846@@ -0,0 +1,14 @@
847+<element name="flavor" ns="http://docs.openstack.org/compute/api/v1.1"
848+ xmlns="http://relaxng.org/ns/structure/1.0">
849+ <attribute name="name"> <text/> </attribute>
850+ <attribute name="id"> <text/> </attribute>
851+ <attribute name="ram"> <text/> </attribute>
852+ <attribute name="disk"> <text/> </attribute>
853+ <attribute name="rxtx_cap"> <text/> </attribute>
854+ <attribute name="rxtx_quota"> <text/> </attribute>
855+ <attribute name="swap"> <text/> </attribute>
856+ <attribute name="vcpus"> <text/> </attribute>
857+ <zeroOrMore>
858+ <externalRef href="../atom-link.rng"/>
859+ </zeroOrMore>
860+</element>
861
862=== added file 'nova/api/openstack/schemas/v1.1/flavors.rng'
863--- nova/api/openstack/schemas/v1.1/flavors.rng 1970-01-01 00:00:00 +0000
864+++ nova/api/openstack/schemas/v1.1/flavors.rng 2011-09-17 02:16:44 +0000
865@@ -0,0 +1,6 @@
866+<element name="flavors" xmlns="http://relaxng.org/ns/structure/1.0"
867+ ns="http://docs.openstack.org/compute/api/v1.1">
868+ <zeroOrMore>
869+ <externalRef href="flavor.rng"/>
870+ </zeroOrMore>
871+</element>
872
873=== added file 'nova/api/openstack/schemas/v1.1/flavors_index.rng'
874--- nova/api/openstack/schemas/v1.1/flavors_index.rng 1970-01-01 00:00:00 +0000
875+++ nova/api/openstack/schemas/v1.1/flavors_index.rng 2011-09-17 02:16:44 +0000
876@@ -0,0 +1,12 @@
877+<element name="flavors" ns="http://docs.openstack.org/compute/api/v1.1"
878+ xmlns="http://relaxng.org/ns/structure/1.0">
879+ <zeroOrMore>
880+ <element name="flavor">
881+ <attribute name="name"> <text/> </attribute>
882+ <attribute name="id"> <text/> </attribute>
883+ <zeroOrMore>
884+ <externalRef href="../atom-link.rng"/>
885+ </zeroOrMore>
886+ </element>
887+ </zeroOrMore>
888+</element>
889
890=== added file 'nova/api/openstack/schemas/v1.1/image.rng'
891--- nova/api/openstack/schemas/v1.1/image.rng 1970-01-01 00:00:00 +0000
892+++ nova/api/openstack/schemas/v1.1/image.rng 2011-09-17 02:16:44 +0000
893@@ -0,0 +1,30 @@
894+<element name="image" ns="http://docs.openstack.org/compute/api/v1.1"
895+ xmlns="http://relaxng.org/ns/structure/1.0">
896+ <attribute name="name"> <text/> </attribute>
897+ <attribute name="id"> <text/> </attribute>
898+ <attribute name="updated"> <text/> </attribute>
899+ <attribute name="created"> <text/> </attribute>
900+ <attribute name="status"> <text/> </attribute>
901+ <optional>
902+ <attribute name="progress"> <text/> </attribute>
903+ </optional>
904+ <optional>
905+ <element name="server">
906+ <attribute name="id"> <text/> </attribute>
907+ <zeroOrMore>
908+ <externalRef href="../atom-link.rng"/>
909+ </zeroOrMore>
910+ </element>
911+ </optional>
912+ <element name="metadata">
913+ <zeroOrMore>
914+ <element name="meta">
915+ <attribute name="key"> <text/> </attribute>
916+ <text/>
917+ </element>
918+ </zeroOrMore>
919+ </element>
920+ <zeroOrMore>
921+ <externalRef href="../atom-link.rng"/>
922+ </zeroOrMore>
923+</element>
924
925=== added file 'nova/api/openstack/schemas/v1.1/images.rng'
926--- nova/api/openstack/schemas/v1.1/images.rng 1970-01-01 00:00:00 +0000
927+++ nova/api/openstack/schemas/v1.1/images.rng 2011-09-17 02:16:44 +0000
928@@ -0,0 +1,6 @@
929+<element name="images" xmlns="http://relaxng.org/ns/structure/1.0"
930+ ns="http://docs.openstack.org/compute/api/v1.1">
931+ <zeroOrMore>
932+ <externalRef href="image.rng"/>
933+ </zeroOrMore>
934+</element>
935
936=== added file 'nova/api/openstack/schemas/v1.1/images_index.rng'
937--- nova/api/openstack/schemas/v1.1/images_index.rng 1970-01-01 00:00:00 +0000
938+++ nova/api/openstack/schemas/v1.1/images_index.rng 2011-09-17 02:16:44 +0000
939@@ -0,0 +1,12 @@
940+<element name="images" ns="http://docs.openstack.org/compute/api/v1.1"
941+ xmlns="http://relaxng.org/ns/structure/1.0">
942+ <zeroOrMore>
943+ <element name="image">
944+ <attribute name="name"> <text/> </attribute>
945+ <attribute name="id"> <text/> </attribute>
946+ <zeroOrMore>
947+ <externalRef href="../atom-link.rng"/>
948+ </zeroOrMore>
949+ </element>
950+ </zeroOrMore>
951+</element>
952
953=== added file 'nova/api/openstack/schemas/v1.1/limits.rng'
954--- nova/api/openstack/schemas/v1.1/limits.rng 1970-01-01 00:00:00 +0000
955+++ nova/api/openstack/schemas/v1.1/limits.rng 2011-09-17 02:16:44 +0000
956@@ -0,0 +1,28 @@
957+<element name="limits" ns="http://docs.openstack.org/compute/api/v1.1"
958+ xmlns="http://relaxng.org/ns/structure/1.0">
959+ <element name="rates">
960+ <zeroOrMore>
961+ <element name="rate">
962+ <attribute name="uri"> <text/> </attribute>
963+ <attribute name="regex"> <text/> </attribute>
964+ <zeroOrMore>
965+ <element name="limit">
966+ <attribute name="value"> <text/> </attribute>
967+ <attribute name="verb"> <text/> </attribute>
968+ <attribute name="remaining"> <text/> </attribute>
969+ <attribute name="unit"> <text/> </attribute>
970+ <attribute name="next-available"> <text/> </attribute>
971+ </element>
972+ </zeroOrMore>
973+ </element>
974+ </zeroOrMore>
975+ </element>
976+ <element name="absolute">
977+ <zeroOrMore>
978+ <element name="limit">
979+ <attribute name="name"> <text/> </attribute>
980+ <attribute name="value"> <text/> </attribute>
981+ </element>
982+ </zeroOrMore>
983+ </element>
984+</element>
985
986=== added file 'nova/api/openstack/schemas/v1.1/metadata.rng'
987--- nova/api/openstack/schemas/v1.1/metadata.rng 1970-01-01 00:00:00 +0000
988+++ nova/api/openstack/schemas/v1.1/metadata.rng 2011-09-17 02:16:44 +0000
989@@ -0,0 +1,9 @@
990+ <element name="metadata" ns="http://docs.openstack.org/compute/api/v1.1"
991+ xmlns="http://relaxng.org/ns/structure/1.0">
992+ <zeroOrMore>
993+ <element name="meta">
994+ <attribute name="key"> <text/> </attribute>
995+ <text/>
996+ </element>
997+ </zeroOrMore>
998+ </element>
999
1000=== modified file 'nova/api/openstack/schemas/v1.1/server.rng'
1001--- nova/api/openstack/schemas/v1.1/server.rng 2011-09-02 19:52:02 +0000
1002+++ nova/api/openstack/schemas/v1.1/server.rng 2011-09-17 02:16:44 +0000
1003@@ -17,9 +17,6 @@
1004 <optional>
1005 <attribute name="adminPass"> <text/> </attribute>
1006 </optional>
1007- <zeroOrMore>
1008- <externalRef href="../atom-link.rng"/>
1009- </zeroOrMore>
1010 <element name="image">
1011 <attribute name="id"> <text/> </attribute>
1012 <externalRef href="../atom-link.rng"/>
1013@@ -49,4 +46,7 @@
1014 </element>
1015 </zeroOrMore>
1016 </element>
1017+ <zeroOrMore>
1018+ <externalRef href="../atom-link.rng"/>
1019+ </zeroOrMore>
1020 </element>
1021
1022=== modified file 'nova/api/openstack/servers.py'
1023--- nova/api/openstack/servers.py 2011-09-06 19:47:09 +0000
1024+++ nova/api/openstack/servers.py 2011-09-17 02:16:44 +0000
1025@@ -17,8 +17,8 @@
1026 import os
1027 import traceback
1028
1029+from lxml import etree
1030 from webob import exc
1031-from xml.dom import minidom
1032 import webob
1033
1034 from nova import compute
1035@@ -38,6 +38,7 @@
1036 import nova.api.openstack.views.flavors
1037 import nova.api.openstack.views.images
1038 import nova.api.openstack.views.servers
1039+from nova.api.openstack import xmlutil
1040
1041
1042 LOG = logging.getLogger('nova.api.openstack.servers')
1043@@ -334,9 +335,8 @@
1044 LOG.exception(msg)
1045 raise exc.HTTPBadRequest(explanation=msg)
1046 try:
1047- # TODO(gundlach): pass reboot_type, support soft reboot in
1048- # virt driver
1049- self.compute_api.reboot(req.environ['nova.context'], id)
1050+ self.compute_api.reboot(req.environ['nova.context'], id,
1051+ reboot_type)
1052 except Exception, e:
1053 LOG.exception(_("Error in reboot %s"), e)
1054 raise exc.HTTPUnprocessableEntity()
1055@@ -851,12 +851,13 @@
1056
1057 class ServerXMLSerializer(wsgi.XMLDictSerializer):
1058
1059- xmlns = wsgi.XMLNS_V11
1060+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
1061
1062 def __init__(self):
1063 self.metadata_serializer = common.MetadataXMLSerializer()
1064 self.addresses_serializer = ips.IPXMLSerializer()
1065
1066+<<<<<<< TREE
1067 def _create_basic_entity_node(self, xml_doc, id, links, name):
1068 basic_node = xml_doc.createElement(name)
1069 basic_node.setAttribute('id', str(id))
1070@@ -939,42 +940,114 @@
1071
1072 def _server_list_to_xml(self, xml_doc, servers, detailed):
1073 container_node = xml_doc.createElement('servers')
1074+=======
1075+ def _create_metadata_node(self, metadata_dict):
1076+ metadata_elem = etree.Element('metadata', nsmap=self.NSMAP)
1077+ self.metadata_serializer.populate_metadata(metadata_elem,
1078+ metadata_dict)
1079+ return metadata_elem
1080+
1081+ def _create_image_node(self, image_dict):
1082+ image_elem = etree.Element('image', nsmap=self.NSMAP)
1083+ image_elem.set('id', str(image_dict['id']))
1084+ for link in image_dict.get('links', []):
1085+ elem = etree.SubElement(image_elem,
1086+ '{%s}link' % xmlutil.XMLNS_ATOM)
1087+ elem.set('rel', link['rel'])
1088+ elem.set('href', link['href'])
1089+ return image_elem
1090+
1091+ def _create_flavor_node(self, flavor_dict):
1092+ flavor_elem = etree.Element('flavor', nsmap=self.NSMAP)
1093+ flavor_elem.set('id', str(flavor_dict['id']))
1094+ for link in flavor_dict.get('links', []):
1095+ elem = etree.SubElement(flavor_elem,
1096+ '{%s}link' % xmlutil.XMLNS_ATOM)
1097+ elem.set('rel', link['rel'])
1098+ elem.set('href', link['href'])
1099+ return flavor_elem
1100+
1101+ def _create_addresses_node(self, addresses_dict):
1102+ addresses_elem = etree.Element('addresses', nsmap=self.NSMAP)
1103+ self.addresses_serializer.populate_addresses_node(addresses_elem,
1104+ addresses_dict)
1105+ return addresses_elem
1106+
1107+ def _populate_server(self, server_elem, server_dict, detailed=False):
1108+ """Populate a server xml element from a dict."""
1109+
1110+ server_elem.set('name', server_dict['name'])
1111+ server_elem.set('id', str(server_dict['id']))
1112+>>>>>>> MERGE-SOURCE
1113 if detailed:
1114- server_to_xml = self._server_to_xml_detailed
1115- else:
1116- server_to_xml = self._server_to_xml
1117-
1118- for server in servers:
1119- item_node = server_to_xml(xml_doc, server)
1120- container_node.appendChild(item_node)
1121- return container_node
1122+ server_elem.set('uuid', str(server_dict['uuid']))
1123+ server_elem.set('userId', str(server_dict['user_id']))
1124+ server_elem.set('tenantId', str(server_dict['tenant_id']))
1125+ server_elem.set('updated', str(server_dict['updated']))
1126+ server_elem.set('created', str(server_dict['created']))
1127+ server_elem.set('hostId', str(server_dict['hostId']))
1128+ server_elem.set('accessIPv4', str(server_dict['accessIPv4']))
1129+ server_elem.set('accessIPv6', str(server_dict['accessIPv6']))
1130+ server_elem.set('status', str(server_dict['status']))
1131+ if 'progress' in server_dict:
1132+ server_elem.set('progress', str(server_dict['progress']))
1133+ image_elem = self._create_image_node(server_dict['image'])
1134+ server_elem.append(image_elem)
1135+
1136+ flavor_elem = self._create_flavor_node(server_dict['flavor'])
1137+ server_elem.append(flavor_elem)
1138+
1139+ meta_elem = self._create_metadata_node(
1140+ server_dict.get('metadata', {}))
1141+ server_elem.append(meta_elem)
1142+
1143+ addresses_elem = self._create_addresses_node(
1144+ server_dict.get('addresses', {}))
1145+ server_elem.append(addresses_elem)
1146+ groups = server_dict.get('security_groups')
1147+ if groups:
1148+ groups_elem = etree.SubElement(server_elem, 'security_groups')
1149+ for group in groups:
1150+ group_elem = etree.SubElement(groups_elem,
1151+ 'security_group')
1152+ group_elem.set('name', group['name'])
1153+
1154+ for link in server_dict.get('links', []):
1155+ elem = etree.SubElement(server_elem,
1156+ '{%s}link' % xmlutil.XMLNS_ATOM)
1157+ elem.set('rel', link['rel'])
1158+ elem.set('href', link['href'])
1159+ return server_elem
1160
1161 def index(self, servers_dict):
1162- xml_doc = minidom.Document()
1163- node = self._server_list_to_xml(xml_doc,
1164- servers_dict['servers'],
1165- detailed=False)
1166- return self.to_xml_string(node, True)
1167+ servers = etree.Element('servers', nsmap=self.NSMAP)
1168+ for server_dict in servers_dict['servers']:
1169+ server = etree.SubElement(servers, 'server')
1170+ self._populate_server(server, server_dict, False)
1171+ return self._to_xml(servers)
1172
1173 def detail(self, servers_dict):
1174- xml_doc = minidom.Document()
1175- node = self._server_list_to_xml(xml_doc,
1176- servers_dict['servers'],
1177- detailed=True)
1178- return self.to_xml_string(node, True)
1179+ servers = etree.Element('servers', nsmap=self.NSMAP)
1180+ for server_dict in servers_dict['servers']:
1181+ server = etree.SubElement(servers, 'server')
1182+ self._populate_server(server, server_dict, True)
1183+ return self._to_xml(servers)
1184
1185 def show(self, server_dict):
1186- xml_doc = minidom.Document()
1187- node = self._server_to_xml_detailed(xml_doc,
1188- server_dict['server'])
1189- return self.to_xml_string(node, True)
1190+ server = etree.Element('server', nsmap=self.NSMAP)
1191+ self._populate_server(server, server_dict['server'], True)
1192+ return self._to_xml(server)
1193
1194 def create(self, server_dict):
1195- xml_doc = minidom.Document()
1196- node = self._server_to_xml_detailed(xml_doc,
1197- server_dict['server'])
1198- node.setAttribute('adminPass', server_dict['server']['adminPass'])
1199- return self.to_xml_string(node, True)
1200+ server = etree.Element('server', nsmap=self.NSMAP)
1201+ self._populate_server(server, server_dict['server'], True)
1202+ server.set('adminPass', server_dict['server']['adminPass'])
1203+ return self._to_xml(server)
1204+
1205+ def action(self, server_dict):
1206+ #NOTE(bcwaldon): We need a way to serialize actions individually. This
1207+ # assumes all actions return a server entity
1208+ return self.create(server_dict)
1209
1210 def action(self, server_dict):
1211 #NOTE(bcwaldon): We need a way to serialize actions individually. This
1212@@ -982,10 +1055,9 @@
1213 return self.create(server_dict)
1214
1215 def update(self, server_dict):
1216- xml_doc = minidom.Document()
1217- node = self._server_to_xml_detailed(xml_doc,
1218- server_dict['server'])
1219- return self.to_xml_string(node, True)
1220+ server = etree.Element('server', nsmap=self.NSMAP)
1221+ self._populate_server(server, server_dict['server'], True)
1222+ return self._to_xml(server)
1223
1224 def _security_group_to_xml(self, doc, security_group):
1225 node = doc.createElement('security_group')
1226
1227=== modified file 'nova/api/openstack/versions.py'
1228--- nova/api/openstack/versions.py 2011-08-03 13:54:00 +0000
1229+++ nova/api/openstack/versions.py 2011-09-17 02:16:44 +0000
1230@@ -16,12 +16,13 @@
1231 # under the License.
1232
1233 from datetime import datetime
1234+from lxml import etree
1235 import webob
1236 import webob.dec
1237-from xml.dom import minidom
1238
1239 import nova.api.openstack.views.versions
1240 from nova.api.openstack import wsgi
1241+from nova.api.openstack import xmlutil
1242
1243
1244 VERSIONS = {
1245@@ -106,7 +107,9 @@
1246 headers_serializer=headers_serializer)
1247
1248 supported_content_types = ('application/json',
1249+ 'application/vnd.openstack.compute+json',
1250 'application/xml',
1251+ 'application/vnd.openstack.compute+xml',
1252 'application/atom+xml')
1253 deserializer = VersionsRequestDeserializer(
1254 supported_content_types=supported_content_types)
1255@@ -159,83 +162,51 @@
1256
1257
1258 class VersionsXMLSerializer(wsgi.XMLDictSerializer):
1259- #TODO(wwolf): this is temporary until we get rid of toprettyxml
1260- # in the base class (XMLDictSerializer), which I plan to do in
1261- # another branch
1262- def to_xml_string(self, node, has_atom=False):
1263- self._add_xmlns(node, has_atom)
1264- return node.toxml(encoding='UTF-8')
1265-
1266- def _versions_to_xml(self, versions, name="versions", xmlns=None):
1267- root = self._xml_doc.createElement(name)
1268- root.setAttribute("xmlns", wsgi.XMLNS_V11)
1269- root.setAttribute("xmlns:atom", wsgi.XMLNS_ATOM)
1270-
1271- for version in versions:
1272- root.appendChild(self._create_version_node(version))
1273-
1274- return root
1275-
1276- def _create_media_types(self, media_types):
1277- base = self._xml_doc.createElement('media-types')
1278- for type in media_types:
1279- node = self._xml_doc.createElement('media-type')
1280- node.setAttribute('base', type['base'])
1281- node.setAttribute('type', type['type'])
1282- base.appendChild(node)
1283-
1284- return base
1285-
1286- def _create_version_node(self, version, create_ns=False):
1287- version_node = self._xml_doc.createElement('version')
1288- if create_ns:
1289- xmlns = wsgi.XMLNS_V11
1290- xmlns_atom = wsgi.XMLNS_ATOM
1291- version_node.setAttribute('xmlns', xmlns)
1292- version_node.setAttribute('xmlns:atom', xmlns_atom)
1293-
1294- version_node.setAttribute('id', version['id'])
1295- version_node.setAttribute('status', version['status'])
1296+
1297+ def _populate_version(self, version_node, version):
1298+ version_node.set('id', version['id'])
1299+ version_node.set('status', version['status'])
1300 if 'updated' in version:
1301- version_node.setAttribute('updated', version['updated'])
1302-
1303+ version_node.set('updated', version['updated'])
1304 if 'media-types' in version:
1305- media_types = self._create_media_types(version['media-types'])
1306- version_node.appendChild(media_types)
1307-
1308- link_nodes = self._create_link_nodes(self._xml_doc, version['links'])
1309- for link in link_nodes:
1310- version_node.appendChild(link)
1311-
1312- return version_node
1313+ media_types = etree.SubElement(version_node, 'media-types')
1314+ for mtype in version['media-types']:
1315+ elem = etree.SubElement(media_types, 'media-type')
1316+ elem.set('base', mtype['base'])
1317+ elem.set('type', mtype['type'])
1318+ for link in version.get('links', []):
1319+ elem = etree.SubElement(version_node,
1320+ '{%s}link' % xmlutil.XMLNS_ATOM)
1321+ elem.set('rel', link['rel'])
1322+ elem.set('href', link['href'])
1323+ if 'type' in link:
1324+ elem.set('type', link['type'])
1325+
1326+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
1327
1328 def index(self, data):
1329- self._xml_doc = minidom.Document()
1330- node = self._versions_to_xml(data['versions'])
1331-
1332- return self.to_xml_string(node)
1333+ root = etree.Element('versions', nsmap=self.NSMAP)
1334+ for version in data['versions']:
1335+ version_elem = etree.SubElement(root, 'version')
1336+ self._populate_version(version_elem, version)
1337+ return self._to_xml(root)
1338
1339 def show(self, data):
1340- self._xml_doc = minidom.Document()
1341- node = self._create_version_node(data['version'], True)
1342-
1343- return self.to_xml_string(node)
1344+ root = etree.Element('version', nsmap=self.NSMAP)
1345+ self._populate_version(root, data['version'])
1346+ return self._to_xml(root)
1347
1348 def multi(self, data):
1349- self._xml_doc = minidom.Document()
1350- node = self._versions_to_xml(data['choices'], 'choices',
1351- xmlns=wsgi.XMLNS_V11)
1352-
1353- return self.to_xml_string(node)
1354+ root = etree.Element('choices', nsmap=self.NSMAP)
1355+ for version in data['choices']:
1356+ version_elem = etree.SubElement(root, 'version')
1357+ self._populate_version(version_elem, version)
1358+ return self._to_xml(root)
1359
1360
1361 class VersionsAtomSerializer(wsgi.XMLDictSerializer):
1362- #TODO(wwolf): this is temporary until we get rid of toprettyxml
1363- # in the base class (XMLDictSerializer), which I plan to do in
1364- # another branch
1365- def to_xml_string(self, node, has_atom=False):
1366- self._add_xmlns(node, has_atom)
1367- return node.toxml(encoding='UTF-8')
1368+
1369+ NSMAP = {None: xmlutil.XMLNS_ATOM}
1370
1371 def __init__(self, metadata=None, xmlns=None):
1372 self.metadata = metadata or {}
1373@@ -244,14 +215,6 @@
1374 else:
1375 self.xmlns = xmlns
1376
1377- def _create_text_elem(self, name, text, type=None):
1378- elem = self._xml_doc.createElement(name)
1379- if type:
1380- elem.setAttribute('type', type)
1381- elem_text = self._xml_doc.createTextNode(text)
1382- elem.appendChild(elem_text)
1383- return elem
1384-
1385 def _get_most_recent_update(self, versions):
1386 recent = None
1387 for version in versions:
1388@@ -269,105 +232,64 @@
1389 link_href = link_href.rstrip('/')
1390 return link_href.rsplit('/', 1)[0] + '/'
1391
1392- def _create_detail_meta(self, root, version):
1393- title = self._create_text_elem('title', "About This Version",
1394- type='text')
1395-
1396- updated = self._create_text_elem('updated', version['updated'])
1397-
1398- uri = version['links'][0]['href']
1399- id = self._create_text_elem('id', uri)
1400-
1401- link = self._xml_doc.createElement('link')
1402- link.setAttribute('rel', 'self')
1403- link.setAttribute('href', uri)
1404-
1405- author = self._xml_doc.createElement('author')
1406- author_name = self._create_text_elem('name', 'Rackspace')
1407- author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/')
1408- author.appendChild(author_name)
1409- author.appendChild(author_uri)
1410-
1411- root.appendChild(title)
1412- root.appendChild(updated)
1413- root.appendChild(id)
1414- root.appendChild(author)
1415- root.appendChild(link)
1416-
1417- def _create_list_meta(self, root, versions):
1418- title = self._create_text_elem('title', "Available API Versions",
1419- type='text')
1420+ def _create_feed(self, versions, feed_title, feed_id):
1421+ feed = etree.Element('feed', nsmap=self.NSMAP)
1422+ title = etree.SubElement(feed, 'title')
1423+ title.set('type', 'text')
1424+ title.text = feed_title
1425+
1426 # Set this updated to the most recently updated version
1427 recent = self._get_most_recent_update(versions)
1428- updated = self._create_text_elem('updated', recent)
1429-
1430- base_url = self._get_base_url(versions[0]['links'][0]['href'])
1431- id = self._create_text_elem('id', base_url)
1432-
1433- link = self._xml_doc.createElement('link')
1434- link.setAttribute('rel', 'self')
1435- link.setAttribute('href', base_url)
1436-
1437- author = self._xml_doc.createElement('author')
1438- author_name = self._create_text_elem('name', 'Rackspace')
1439- author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/')
1440- author.appendChild(author_name)
1441- author.appendChild(author_uri)
1442-
1443- root.appendChild(title)
1444- root.appendChild(updated)
1445- root.appendChild(id)
1446- root.appendChild(author)
1447- root.appendChild(link)
1448-
1449- def _create_version_entries(self, root, versions):
1450+ etree.SubElement(feed, 'updated').text = recent
1451+
1452+ etree.SubElement(feed, 'id').text = feed_id
1453+
1454+ link = etree.SubElement(feed, 'link')
1455+ link.set('rel', 'self')
1456+ link.set('href', feed_id)
1457+
1458+ author = etree.SubElement(feed, 'author')
1459+ etree.SubElement(author, 'name').text = 'Rackspace'
1460+ etree.SubElement(author, 'uri').text = 'http://www.rackspace.com/'
1461+
1462 for version in versions:
1463- entry = self._xml_doc.createElement('entry')
1464-
1465- id = self._create_text_elem('id', version['links'][0]['href'])
1466- title = self._create_text_elem('title',
1467- 'Version %s' % version['id'],
1468- type='text')
1469- updated = self._create_text_elem('updated', version['updated'])
1470-
1471- entry.appendChild(id)
1472- entry.appendChild(title)
1473- entry.appendChild(updated)
1474-
1475- for link in version['links']:
1476- link_node = self._xml_doc.createElement('link')
1477- link_node.setAttribute('rel', link['rel'])
1478- link_node.setAttribute('href', link['href'])
1479- if 'type' in link:
1480- link_node.setAttribute('type', link['type'])
1481-
1482- entry.appendChild(link_node)
1483-
1484- content = self._create_text_elem('content',
1485- 'Version %s %s (%s)' %
1486- (version['id'],
1487- version['status'],
1488- version['updated']),
1489- type='text')
1490-
1491- entry.appendChild(content)
1492- root.appendChild(entry)
1493+ feed.append(self._create_version_entry(version))
1494+
1495+ return feed
1496+
1497+ def _create_version_entry(self, version):
1498+ entry = etree.Element('entry')
1499+ etree.SubElement(entry, 'id').text = version['links'][0]['href']
1500+ title = etree.SubElement(entry, 'title')
1501+ title.set('type', 'text')
1502+ title.text = 'Version %s' % version['id']
1503+ etree.SubElement(entry, 'updated').text = version['updated']
1504+
1505+ for link in version['links']:
1506+ link_elem = etree.SubElement(entry, 'link')
1507+ link_elem.set('rel', link['rel'])
1508+ link_elem.set('href', link['href'])
1509+ if 'type' in link:
1510+ link_elem.set('type', link['type'])
1511+
1512+ content = etree.SubElement(entry, 'content')
1513+ content.set('type', 'text')
1514+ content.text = 'Version %s %s (%s)' % (version['id'],
1515+ version['status'],
1516+ version['updated'])
1517+ return entry
1518
1519 def index(self, data):
1520- self._xml_doc = minidom.Document()
1521- node = self._xml_doc.createElementNS(self.xmlns, 'feed')
1522- self._create_list_meta(node, data['versions'])
1523- self._create_version_entries(node, data['versions'])
1524-
1525- return self.to_xml_string(node)
1526+ versions = data['versions']
1527+ feed_id = self._get_base_url(versions[0]['links'][0]['href'])
1528+ feed = self._create_feed(versions, 'Available API Versions', feed_id)
1529+ return self._to_xml(feed)
1530
1531 def show(self, data):
1532- self._xml_doc = minidom.Document()
1533- node = self._xml_doc.createElementNS(self.xmlns, 'feed')
1534- self._create_detail_meta(node, data['version'])
1535- self._create_version_entries(node, [data['version']])
1536-
1537- return self.to_xml_string(node)
1538+ version = data['version']
1539+ feed_id = version['links'][0]['href']
1540+ feed = self._create_feed([version], 'About This Version', feed_id)
1541+ return self._to_xml(feed)
1542
1543
1544 class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer):
1545@@ -388,7 +310,9 @@
1546 serializer = wsgi.ResponseSerializer(body_serializers)
1547
1548 supported_content_types = ('application/json',
1549+ 'application/vnd.openstack.compute+json',
1550 'application/xml',
1551+ 'application/vnd.openstack.compute+xml',
1552 'application/atom+xml')
1553 deserializer = wsgi.RequestDeserializer(
1554 supported_content_types=supported_content_types)
1555
1556=== modified file 'nova/api/openstack/views/flavors.py'
1557--- nova/api/openstack/views/flavors.py 2011-08-09 23:26:35 +0000
1558+++ nova/api/openstack/views/flavors.py 2011-09-17 02:16:44 +0000
1559@@ -50,6 +50,9 @@
1560 "disk": flavor_obj["local_gb"],
1561 }
1562
1563+ for key in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
1564+ detail[key] = flavor_obj.get(key, "")
1565+
1566 detail.update(simple)
1567
1568 return detail
1569
1570=== modified file 'nova/api/openstack/views/images.py'
1571--- nova/api/openstack/views/images.py 2011-09-09 21:25:45 +0000
1572+++ nova/api/openstack/views/images.py 2011-09-17 02:16:44 +0000
1573@@ -71,6 +71,7 @@
1574 }
1575
1576 self._build_server(image, image_obj)
1577+ self._build_image_id(image, image_obj)
1578
1579 if detail:
1580 image.update({
1581@@ -96,6 +97,12 @@
1582 except (KeyError, ValueError):
1583 pass
1584
1585+ def _build_image_id(self, image, image_obj):
1586+ try:
1587+ image['id'] = int(image_obj['id'])
1588+ except ValueError:
1589+ pass
1590+
1591
1592 class ViewBuilderV11(ViewBuilder):
1593 """OpenStack API v1.1 Image Builder"""
1594@@ -119,6 +126,9 @@
1595 except KeyError:
1596 return
1597
1598+ def _build_image_id(self, image, image_obj):
1599+ image['id'] = "%s" % image_obj['id']
1600+
1601 def generate_href(self, image_id):
1602 """Return an href string pointing to this object."""
1603 return os.path.join(self.base_url, self.project_id,
1604
1605=== modified file 'nova/api/openstack/views/versions.py'
1606--- nova/api/openstack/views/versions.py 2011-08-03 13:54:00 +0000
1607+++ nova/api/openstack/views/versions.py 2011-09-17 02:16:44 +0000
1608@@ -52,7 +52,7 @@
1609
1610 def build_versions(self, versions):
1611 version_objs = []
1612- for version in versions:
1613+ for version in sorted(versions.keys()):
1614 version = versions[version]
1615 version_objs.append({
1616 "id": version['id'],
1617
1618=== modified file 'nova/api/openstack/wsgi.py'
1619--- nova/api/openstack/wsgi.py 2011-08-23 17:08:42 +0000
1620+++ nova/api/openstack/wsgi.py 2011-09-17 02:16:44 +0000
1621@@ -1,5 +1,22 @@
1622+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1623+
1624+# Copyright 2011 OpenStack LLC.
1625+# All Rights Reserved.
1626+#
1627+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1628+# not use this file except in compliance with the License. You may obtain
1629+# a copy of the License at
1630+#
1631+# http://www.apache.org/licenses/LICENSE-2.0
1632+#
1633+# Unless required by applicable law or agreed to in writing, software
1634+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1635+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1636+# License for the specific language governing permissions and limitations
1637+# under the License.
1638
1639 import json
1640+from lxml import etree
1641 import webob
1642 from xml.dom import minidom
1643 from xml.parsers import expat
1644@@ -18,6 +35,21 @@
1645
1646 LOG = logging.getLogger('nova.api.openstack.wsgi')
1647
1648+# The vendor content types should serialize identically to the non-vendor
1649+# content types. So to avoid littering the code with both options, we
1650+# map the vendor to the other when looking up the type
1651+_CONTENT_TYPE_MAP = {
1652+ 'application/vnd.openstack.compute+json': 'application/json',
1653+ 'application/vnd.openstack.compute+xml': 'application/xml',
1654+}
1655+
1656+_SUPPORTED_CONTENT_TYPES = (
1657+ 'application/json',
1658+ 'application/vnd.openstack.compute+json',
1659+ 'application/xml',
1660+ 'application/vnd.openstack.compute+xml',
1661+)
1662+
1663
1664 class Request(webob.Request):
1665 """Add some Openstack API-specific logic to the base webob.Request."""
1666@@ -29,7 +61,7 @@
1667
1668 """
1669 supported_content_types = supported_content_types or \
1670- ('application/json', 'application/xml')
1671+ _SUPPORTED_CONTENT_TYPES
1672
1673 parts = self.path.rsplit('.', 1)
1674 if len(parts) > 1:
1675@@ -51,7 +83,7 @@
1676 if not "Content-Type" in self.headers:
1677 return None
1678
1679- allowed_types = ("application/xml", "application/json")
1680+ allowed_types = _SUPPORTED_CONTENT_TYPES
1681 content_type = self.content_type
1682
1683 if content_type not in allowed_types:
1684@@ -191,7 +223,7 @@
1685 supported_content_types=None):
1686
1687 self.supported_content_types = supported_content_types or \
1688- ('application/json', 'application/xml')
1689+ _SUPPORTED_CONTENT_TYPES
1690
1691 self.body_deserializers = {
1692 'application/xml': XMLDeserializer(),
1693@@ -249,7 +281,8 @@
1694
1695 def get_body_deserializer(self, content_type):
1696 try:
1697- return self.body_deserializers[content_type]
1698+ ctype = _CONTENT_TYPE_MAP.get(content_type, content_type)
1699+ return self.body_deserializers[ctype]
1700 except (KeyError, TypeError):
1701 raise exception.InvalidContentType(content_type=content_type)
1702
1703@@ -315,7 +348,7 @@
1704
1705 def to_xml_string(self, node, has_atom=False):
1706 self._add_xmlns(node, has_atom)
1707- return node.toprettyxml(indent=' ', encoding='UTF-8')
1708+ return node.toxml('UTF-8')
1709
1710 #NOTE (ameade): the has_atom should be removed after all of the
1711 # xml serializers and view builders have been updated to the current
1712@@ -392,6 +425,10 @@
1713 link_nodes.append(link_node)
1714 return link_nodes
1715
1716+ def _to_xml(self, root):
1717+ """Convert the xml object to an xml string."""
1718+ return etree.tostring(root, encoding='UTF-8', xml_declaration=True)
1719+
1720
1721 class ResponseHeadersSerializer(ActionDispatcher):
1722 """Default response headers serialization"""
1723@@ -439,7 +476,8 @@
1724
1725 def get_body_serializer(self, content_type):
1726 try:
1727- return self.body_serializers[content_type]
1728+ ctype = _CONTENT_TYPE_MAP.get(content_type, content_type)
1729+ return self.body_serializers[ctype]
1730 except (KeyError, TypeError):
1731 raise exception.InvalidContentType(content_type=content_type)
1732
1733
1734=== modified file 'nova/compute/api.py'
1735--- nova/compute/api.py 2011-09-12 20:32:09 +0000
1736+++ nova/compute/api.py 2011-09-17 02:16:44 +0000
1737@@ -1043,13 +1043,22 @@
1738 return recv_meta
1739
1740 @scheduler_api.reroute_compute("reboot")
1741- def reboot(self, context, instance_id):
1742+ def reboot(self, context, instance_id, reboot_type):
1743 """Reboot the given instance."""
1744+<<<<<<< TREE
1745 self.update(context,
1746 instance_id,
1747 vm_state=vm_states.ACTIVE,
1748 task_state=task_states.REBOOTING)
1749 self._cast_compute_message('reboot_instance', context, instance_id)
1750+=======
1751+ self.update(context,
1752+ instance_id,
1753+ vm_state=vm_states.ACTIVE,
1754+ task_state=task_states.REBOOTING)
1755+ self._cast_compute_message('reboot_instance', context, instance_id,
1756+ params={'reboot_type': reboot_type})
1757+>>>>>>> MERGE-SOURCE
1758
1759 @scheduler_api.reroute_compute("rebuild")
1760 def rebuild(self, context, instance_id, image_href, admin_password,
1761
1762=== modified file 'nova/compute/manager.py'
1763--- nova/compute/manager.py 2011-09-12 15:00:09 +0000
1764+++ nova/compute/manager.py 2011-09-17 02:16:44 +0000
1765@@ -580,7 +580,7 @@
1766
1767 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1768 @checks_instance_lock
1769- def reboot_instance(self, context, instance_id):
1770+ def reboot_instance(self, context, instance_id, reboot_type="SOFT"):
1771 """Reboot an instance on this host."""
1772 LOG.audit(_("Rebooting instance %s"), instance_id, context=context)
1773 context = context.elevated()
1774@@ -602,6 +602,7 @@
1775 context=context)
1776
1777 network_info = self._get_instance_nw_info(context, instance_ref)
1778+<<<<<<< TREE
1779 self.driver.reboot(instance_ref, network_info)
1780
1781 current_power_state = self._get_power_state(context, instance_ref)
1782@@ -610,6 +611,16 @@
1783 power_state=current_power_state,
1784 vm_state=vm_states.ACTIVE,
1785 task_state=None)
1786+=======
1787+ self.driver.reboot(instance_ref, network_info, reboot_type)
1788+
1789+ current_power_state = self._get_power_state(context, instance_ref)
1790+ self._instance_update(context,
1791+ instance_id,
1792+ power_state=current_power_state,
1793+ vm_state=vm_states.ACTIVE,
1794+ task_state=None)
1795+>>>>>>> MERGE-SOURCE
1796
1797 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1798 def snapshot_instance(self, context, instance_id, image_id,
1799
1800=== modified file 'nova/image/fake.py'
1801--- nova/image/fake.py 2011-08-05 13:37:36 +0000
1802+++ nova/image/fake.py 2011-09-17 02:16:44 +0000
1803@@ -24,7 +24,6 @@
1804 from nova import exception
1805 from nova import flags
1806 from nova import log as logging
1807-from nova.image import service
1808
1809
1810 LOG = logging.getLogger('nova.image.fake')
1811@@ -33,7 +32,7 @@
1812 FLAGS = flags.FLAGS
1813
1814
1815-class _FakeImageService(service.BaseImageService):
1816+class _FakeImageService(object):
1817 """Mock (fake) image service for unit testing."""
1818
1819 def __init__(self):
1820
1821=== modified file 'nova/image/glance.py'
1822--- nova/image/glance.py 2011-09-12 19:18:57 +0000
1823+++ nova/image/glance.py 2011-09-17 02:16:44 +0000
1824@@ -31,7 +31,6 @@
1825 from nova import flags
1826 from nova import log as logging
1827 from nova import utils
1828-from nova.image import service
1829
1830
1831 LOG = logging.getLogger('nova.image.glance')
1832@@ -87,6 +86,7 @@
1833 return host, port
1834
1835
1836+<<<<<<< TREE
1837 def get_glance_client(context, image_href):
1838 """Get the correct glance client and id for the given image_href.
1839
1840@@ -115,16 +115,38 @@
1841
1842
1843 class GlanceImageService(service.BaseImageService):
1844+=======
1845+def get_glance_client(context, image_href):
1846+ """Get the correct glance client and id for the given image_href.
1847+
1848+ The image_href param can be an href of the form
1849+ http://myglanceserver:9292/images/42, or just an int such as 42. If the
1850+ image_href is an int, then flags are used to create the default
1851+ glance client.
1852+
1853+ :param image_href: image ref/id for an image
1854+ :returns: a tuple of the form (glance_client, image_id)
1855+
1856+ """
1857+ image_href = image_href or 0
1858+ if str(image_href).isdigit():
1859+ glance_host, glance_port = pick_glance_api_server()
1860+ glance_client = _create_glance_client(context, glance_host,
1861+ glance_port)
1862+ return (glance_client, int(image_href))
1863+
1864+ try:
1865+ (image_id, host, port) = _parse_image_ref(image_href)
1866+ except ValueError:
1867+ raise exception.InvalidImageRef(image_href=image_href)
1868+ glance_client = _create_glance_client(context, glance_host, glance_port)
1869+ return (glance_client, image_id)
1870+
1871+
1872+class GlanceImageService(object):
1873+>>>>>>> MERGE-SOURCE
1874 """Provides storage and retrieval of disk image objects within Glance."""
1875
1876- GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format',
1877- 'container_format', 'checksum']
1878-
1879- # NOTE(sirp): Overriding to use _translate_to_service provided by
1880- # BaseImageService
1881- SERVICE_IMAGE_ATTRS = service.BaseImageService.BASE_IMAGE_ATTRS +\
1882- GLANCE_ONLY_ATTRS
1883-
1884 def __init__(self, client=None):
1885 self._client = client
1886
1887@@ -160,7 +182,7 @@
1888 images = []
1889 for image_meta in image_metas:
1890 if self._is_image_available(context, image_meta):
1891- base_image_meta = self._translate_to_base(image_meta)
1892+ base_image_meta = self._translate_from_glance(image_meta)
1893 images.append(base_image_meta)
1894 return images
1895
1896@@ -224,7 +246,7 @@
1897 if not self._is_image_available(context, image_meta):
1898 raise exception.ImageNotFound(image_id=image_id)
1899
1900- base_image_meta = self._translate_to_base(image_meta)
1901+ base_image_meta = self._translate_from_glance(image_meta)
1902 return base_image_meta
1903
1904 def show_by_name(self, context, name):
1905@@ -248,7 +270,7 @@
1906 for chunk in image_chunks:
1907 data.write(chunk)
1908
1909- base_image_meta = self._translate_to_base(image_meta)
1910+ base_image_meta = self._translate_from_glance(image_meta)
1911 return base_image_meta
1912
1913 def create(self, context, image_meta, data=None):
1914@@ -260,7 +282,7 @@
1915 # Translate Base -> Service
1916 LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
1917 image_meta)
1918- sent_service_image_meta = self._translate_to_service(image_meta)
1919+ sent_service_image_meta = self._translate_to_glance(image_meta)
1920 LOG.debug(_('Metadata after formatting for Glance %s'),
1921 sent_service_image_meta)
1922
1923@@ -268,7 +290,7 @@
1924 sent_service_image_meta, data)
1925
1926 # Translate Service -> Base
1927- base_image_meta = self._translate_to_base(recv_service_image_meta)
1928+ base_image_meta = self._translate_from_glance(recv_service_image_meta)
1929 LOG.debug(_('Metadata returned from Glance formatted for Base %s'),
1930 base_image_meta)
1931 return base_image_meta
1932@@ -281,14 +303,14 @@
1933 """
1934 # NOTE(vish): show is to check if image is available
1935 self.show(context, image_id)
1936- image_meta = _convert_to_string(image_meta)
1937+ image_meta = self._translate_to_glance(image_meta)
1938 try:
1939 client = self._get_client(context)
1940 image_meta = client.update_image(image_id, image_meta, data)
1941 except glance_exception.NotFound:
1942 raise exception.ImageNotFound(image_id=image_id)
1943
1944- base_image_meta = self._translate_to_base(image_meta)
1945+ base_image_meta = self._translate_from_glance(image_meta)
1946 return base_image_meta
1947
1948 def delete(self, context, image_id):
1949@@ -310,21 +332,19 @@
1950 pass
1951
1952 @classmethod
1953- def _translate_to_service(cls, image_meta):
1954- image_meta = super(GlanceImageService,
1955- cls)._translate_to_service(image_meta)
1956+ def _translate_to_glance(cls, image_meta):
1957 image_meta = _convert_to_string(image_meta)
1958+ image_meta = _remove_read_only(image_meta)
1959 return image_meta
1960
1961 @classmethod
1962- def _translate_to_base(cls, image_meta):
1963- """Override translation to handle conversion to datetime objects."""
1964- image_meta = service.BaseImageService._propertify_metadata(
1965- image_meta, cls.SERVICE_IMAGE_ATTRS)
1966+ def _translate_from_glance(cls, image_meta):
1967+ image_meta = _limit_attributes(image_meta)
1968 image_meta = _convert_timestamps_to_datetimes(image_meta)
1969 image_meta = _convert_from_string(image_meta)
1970 return image_meta
1971
1972+<<<<<<< TREE
1973 @staticmethod
1974 def _is_image_available(context, image_meta):
1975 """Check image availability.
1976@@ -339,6 +359,34 @@
1977 return service.BaseImageService._is_image_available(context,
1978 image_meta)
1979
1980+=======
1981+ @staticmethod
1982+ def _is_image_available(context, image_meta):
1983+ """Check image availability.
1984+
1985+ Under Glance, images are always available if the context has
1986+ an auth_token.
1987+
1988+ """
1989+ if hasattr(context, 'auth_token') and context.auth_token:
1990+ return True
1991+
1992+ if image_meta['is_public'] or context.is_admin:
1993+ return True
1994+
1995+ properties = image_meta['properties']
1996+
1997+ if context.project_id and ('project_id' in properties):
1998+ return str(properties['project_id']) == str(context.project_id)
1999+
2000+ try:
2001+ user_id = properties['user_id']
2002+ except KeyError:
2003+ return False
2004+
2005+ return str(user_id) == str(context.user_id)
2006+
2007+>>>>>>> MERGE-SOURCE
2008
2009 # utility functions
2010 def _convert_timestamps_to_datetimes(image_meta):
2011@@ -397,3 +445,27 @@
2012
2013 def _convert_to_string(metadata):
2014 return _convert(_json_dumps, metadata)
2015+
2016+
2017+def _limit_attributes(image_meta):
2018+ IMAGE_ATTRIBUTES = ['size', 'location', 'disk_format',
2019+ 'container_format', 'checksum', 'id',
2020+ 'name', 'created_at', 'updated_at',
2021+ 'deleted_at', 'deleted', 'status',
2022+ 'is_public']
2023+ output = {}
2024+ for attr in IMAGE_ATTRIBUTES:
2025+ output[attr] = image_meta.get(attr)
2026+
2027+ output['properties'] = image_meta.get('properties', {})
2028+
2029+ return output
2030+
2031+
2032+def _remove_read_only(image_meta):
2033+ IMAGE_ATTRIBUTES = ['updated_at', 'created_at', 'deleted_at']
2034+ output = copy.deepcopy(image_meta)
2035+ for attr in IMAGE_ATTRIBUTES:
2036+ if attr in output:
2037+ del output[attr]
2038+ return output
2039
2040=== modified file 'nova/image/s3.py'
2041--- nova/image/s3.py 2011-08-16 00:12:03 +0000
2042+++ nova/image/s3.py 2011-09-17 02:16:44 +0000
2043@@ -34,7 +34,6 @@
2044 from nova import image
2045 from nova import log as logging
2046 from nova import utils
2047-from nova.image import service
2048 from nova.api.ec2 import ec2utils
2049
2050
2051@@ -48,7 +47,7 @@
2052 'secret key to use for s3 server for images')
2053
2054
2055-class S3ImageService(service.BaseImageService):
2056+class S3ImageService(object):
2057 """Wraps an existing image service to support s3 based register."""
2058
2059 def __init__(self, service=None, *args, **kwargs):
2060
2061=== removed file 'nova/image/service.py'
2062--- nova/image/service.py 2011-05-25 21:28:10 +0000
2063+++ nova/image/service.py 1970-01-01 00:00:00 +0000
2064@@ -1,200 +0,0 @@
2065-# vim: tabstop=4 shiftwidth=4 softtabstop=4
2066-
2067-# Copyright 2010 OpenStack LLC.
2068-# All Rights Reserved.
2069-#
2070-# Licensed under the Apache License, Version 2.0 (the "License"); you may
2071-# not use this file except in compliance with the License. You may obtain
2072-# a copy of the License at
2073-#
2074-# http://www.apache.org/licenses/LICENSE-2.0
2075-#
2076-# Unless required by applicable law or agreed to in writing, software
2077-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2078-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2079-# License for the specific language governing permissions and limitations
2080-# under the License.
2081-
2082-
2083-from nova import utils
2084-
2085-
2086-class BaseImageService(object):
2087- """Base class for providing image search and retrieval services.
2088-
2089- ImageService exposes two concepts of metadata:
2090-
2091- 1. First-class attributes: This is metadata that is common to all
2092- ImageService subclasses and is shared across all hypervisors. These
2093- attributes are defined by IMAGE_ATTRS.
2094-
2095- 2. Properties: This is metdata that is specific to an ImageService,
2096- and Image, or a particular hypervisor. Any attribute not present in
2097- BASE_IMAGE_ATTRS should be considered an image property.
2098-
2099- This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the
2100- metadata dict, all other attributes will be returned as keys in the nested
2101- 'properties' dict.
2102-
2103- """
2104-
2105- BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at',
2106- 'deleted_at', 'deleted', 'status', 'is_public']
2107-
2108- # NOTE(sirp): ImageService subclasses may override this to aid translation
2109- # between BaseImageService attributes and additional metadata stored by
2110- # the ImageService subclass
2111- SERVICE_IMAGE_ATTRS = []
2112-
2113- def index(self, context, *args, **kwargs):
2114- """List images.
2115-
2116- :returns: a sequence of mappings with the following signature
2117- {'id': opaque id of image, 'name': name of image}
2118-
2119- """
2120- raise NotImplementedError
2121-
2122- def detail(self, context, *args, **kwargs):
2123- """Detailed information about an images.
2124-
2125- :returns: a sequence of mappings with the following signature
2126- {'id': opaque id of image,
2127- 'name': name of image,
2128- 'created_at': creation datetime object,
2129- 'updated_at': modification datetime object,
2130- 'deleted_at': deletion datetime object or None,
2131- 'deleted': boolean indicating if image has been deleted,
2132- 'status': string description of image status,
2133- 'is_public': boolean indicating if image is public
2134- }
2135-
2136- If the service does not implement a method that provides a detailed
2137- set of information about images, then the method should raise
2138- NotImplementedError, in which case Nova will emulate this method
2139- with repeated calls to show() for each image received from the
2140- index() method.
2141-
2142- """
2143- raise NotImplementedError
2144-
2145- def show(self, context, image_id):
2146- """Detailed information about an image.
2147-
2148- :returns: a mapping with the following signature:
2149- {'id': opaque id of image,
2150- 'name': name of image,
2151- 'created_at': creation datetime object,
2152- 'updated_at': modification datetime object,
2153- 'deleted_at': deletion datetime object or None,
2154- 'deleted': boolean indicating if image has been deleted,
2155- 'status': string description of image status,
2156- 'is_public': boolean indicating if image is public
2157- }, ...
2158-
2159- :raises: NotFound if the image does not exist
2160-
2161- """
2162- raise NotImplementedError
2163-
2164- def get(self, context, data):
2165- """Get an image.
2166-
2167- :param data: a file-like object to hold binary image data
2168- :returns: a dict containing image metadata, writes image data to data.
2169- :raises: NotFound if the image does not exist
2170-
2171- """
2172- raise NotImplementedError
2173-
2174- def create(self, context, metadata, data=None):
2175- """Store the image metadata and data.
2176-
2177- :returns: the new image metadata.
2178- :raises: AlreadyExists if the image already exist.
2179-
2180- """
2181- raise NotImplementedError
2182-
2183- def update(self, context, image_id, metadata, data=None):
2184- """Update the given image metadata and data and return the metadata.
2185-
2186- :raises: NotFound if the image does not exist.
2187-
2188- """
2189- raise NotImplementedError
2190-
2191- def delete(self, context, image_id):
2192- """Delete the given image.
2193-
2194- :raises: NotFound if the image does not exist.
2195-
2196- """
2197- raise NotImplementedError
2198-
2199- @staticmethod
2200- def _is_image_available(context, image_meta):
2201- """Check image availability.
2202-
2203- Images are always available if they are public or if the user is an
2204- admin.
2205-
2206- Otherwise, we filter by project_id (if present) and then fall-back to
2207- images owned by user.
2208-
2209- """
2210- # FIXME(sirp): We should be filtering by user_id on the Glance side
2211- # for security; however, we can't do that until we get authn/authz
2212- # sorted out. Until then, filtering in Nova.
2213- if image_meta['is_public'] or context.is_admin:
2214- return True
2215-
2216- properties = image_meta['properties']
2217-
2218- if context.project_id and ('project_id' in properties):
2219- return str(properties['project_id']) == str(context.project_id)
2220-
2221- try:
2222- user_id = properties['user_id']
2223- except KeyError:
2224- return False
2225-
2226- return str(user_id) == str(context.user_id)
2227-
2228- @classmethod
2229- def _translate_to_base(cls, metadata):
2230- """Return a metadata dictionary that is BaseImageService compliant.
2231-
2232- This is used by subclasses to expose only a metadata dictionary that
2233- is the same across ImageService implementations.
2234-
2235- """
2236- return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS)
2237-
2238- @classmethod
2239- def _translate_to_service(cls, metadata):
2240- """Return a metadata dict that is usable by the ImageService subclass.
2241-
2242- As an example, Glance has additional attributes (like 'location'); the
2243- BaseImageService considers these properties, but we need to translate
2244- these back to first-class attrs for sending to Glance. This method
2245- handles this by allowing you to specify the attributes an ImageService
2246- considers first-class.
2247-
2248- """
2249- if not cls.SERVICE_IMAGE_ATTRS:
2250- raise NotImplementedError(_('Cannot use this without specifying '
2251- 'SERVICE_IMAGE_ATTRS for subclass'))
2252- return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS)
2253-
2254- @staticmethod
2255- def _propertify_metadata(metadata, keys):
2256- """Move unknown keys to a nested 'properties' dict.
2257-
2258- :returns: a new dict with the keys moved.
2259-
2260- """
2261- flattened = utils.flatten_dict(metadata)
2262- attributes, properties = utils.partition_dict(flattened, keys)
2263- attributes['properties'] = properties
2264- return attributes
2265
2266=== modified file 'nova/scheduler/abstract_scheduler.py'
2267--- nova/scheduler/abstract_scheduler.py 2011-09-08 20:27:33 +0000
2268+++ nova/scheduler/abstract_scheduler.py 2011-09-17 02:16:44 +0000
2269@@ -20,8 +20,8 @@
2270 behavior is to simply select all hosts and weight them the same.
2271 """
2272
2273+import json
2274 import operator
2275-import json
2276
2277 import M2Crypto
2278
2279
2280=== modified file 'nova/scheduler/base_scheduler.py'
2281--- nova/scheduler/base_scheduler.py 2011-09-07 18:37:29 +0000
2282+++ nova/scheduler/base_scheduler.py 2011-09-17 02:16:44 +0000
2283@@ -27,6 +27,8 @@
2284 from nova.scheduler import host_filter
2285
2286 FLAGS = flags.FLAGS
2287+flags.DEFINE_boolean('spread_first', False,
2288+ 'Use a spread-first zone scheduler strategy')
2289 LOG = logging.getLogger('nova.scheduler.base_scheduler')
2290
2291
2292@@ -55,17 +57,39 @@
2293 scheduling objectives
2294 """
2295 # NOTE(sirp): The default logic is the same as the NoopCostFunction
2296- hosts = [dict(weight=1, hostname=hostname, capabilities=capabilities)
2297- for hostname, capabilities in hosts]
2298-
2299- # NOTE(Vek): What we actually need to return is enough hosts
2300- # for all the instances!
2301- num_instances = request_spec.get('num_instances', 1)
2302- instances = []
2303- while num_instances > len(hosts):
2304- instances.extend(hosts)
2305- num_instances -= len(hosts)
2306- if num_instances > 0:
2307- instances.extend(hosts[:num_instances])
2308-
2309- return instances
2310+<<<<<<< TREE
2311+ hosts = [dict(weight=1, hostname=hostname, capabilities=capabilities)
2312+ for hostname, capabilities in hosts]
2313+
2314+ # NOTE(Vek): What we actually need to return is enough hosts
2315+ # for all the instances!
2316+ num_instances = request_spec.get('num_instances', 1)
2317+ instances = []
2318+ while num_instances > len(hosts):
2319+ instances.extend(hosts)
2320+ num_instances -= len(hosts)
2321+ if num_instances > 0:
2322+ instances.extend(hosts[:num_instances])
2323+
2324+ return instances
2325+=======
2326+ hosts = [dict(weight=1, hostname=hostname, capabilities=capabilities)
2327+ for hostname, capabilities in hosts]
2328+
2329+ # NOTE(Vek): What we actually need to return is enough hosts
2330+ # for all the instances!
2331+ num_instances = request_spec.get('num_instances', 1)
2332+ instances = []
2333+ while num_instances > len(hosts):
2334+ instances.extend(hosts)
2335+ num_instances -= len(hosts)
2336+ if num_instances > 0:
2337+ instances.extend(hosts[:num_instances])
2338+
2339+ # Adjust the weights for a spread-first strategy
2340+ if FLAGS.spread_first:
2341+ for i, host in enumerate(hosts):
2342+ host['weight'] = i + 1
2343+
2344+ return instances
2345+>>>>>>> MERGE-SOURCE
2346
2347=== renamed directory 'nova/tests/public_key' => 'nova/tests/api/ec2/public_key'
2348=== renamed file 'nova/tests/test_cloud.py' => 'nova/tests/api/ec2/test_cloud.py'
2349=== modified file 'nova/tests/api/openstack/common.py'
2350--- nova/tests/api/openstack/common.py 2011-03-09 20:08:11 +0000
2351+++ nova/tests/api/openstack/common.py 2011-09-17 02:16:44 +0000
2352@@ -34,3 +34,25 @@
2353 req.body = json.dumps(body)
2354 return req
2355 return web_request
2356+
2357+
2358+def compare_links(actual, expected):
2359+ """Compare xml atom links."""
2360+
2361+ return compare_tree_to_dict(actual, expected, ('rel', 'href', 'type'))
2362+
2363+
2364+def compare_media_types(actual, expected):
2365+ """Compare xml media types."""
2366+
2367+ return compare_tree_to_dict(actual, expected, ('base', 'type'))
2368+
2369+
2370+def compare_tree_to_dict(actual, expected, keys):
2371+ """Compare parts of lxml.etree objects to dicts."""
2372+
2373+ for elem, data in zip(actual, expected):
2374+ for key in keys:
2375+ if elem.get(key) != data.get(key):
2376+ return False
2377+ return True
2378
2379=== modified file 'nova/tests/api/openstack/contrib/test_createserverext.py'
2380--- nova/tests/api/openstack/contrib/test_createserverext.py 2011-09-06 19:47:09 +0000
2381+++ nova/tests/api/openstack/contrib/test_createserverext.py 2011-09-17 02:16:44 +0000
2382@@ -45,6 +45,7 @@
2383
2384 INVALID_NETWORKS = [('invalid', 'invalid-ip-address')]
2385
2386+<<<<<<< TREE
2387 INSTANCE = {
2388 "id": 1,
2389 "display_name": "test_server",
2390@@ -77,6 +78,44 @@
2391 security_group_id):
2392 pass
2393
2394+=======
2395+INSTANCE = {
2396+ "id": 1,
2397+ "display_name": "test_server",
2398+ "uuid": FAKE_UUID,
2399+ "user_id": 'fake_user_id',
2400+ "tenant_id": 'fake_tenant_id',
2401+ "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
2402+ "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
2403+ "security_groups": [{"id": 1, "name": "test"}],
2404+ "image_ref": 'http://foo.com/123',
2405+ "instance_type": {"flavorid": '124'},
2406+ }
2407+
2408+
2409+def return_server_by_id(context, id, session=None):
2410+ INSTANCE['id'] = id
2411+ return INSTANCE
2412+
2413+
2414+def return_security_group_non_existing(context, project_id, group_name):
2415+ raise exception.SecurityGroupNotFoundForProject(project_id=project_id,
2416+ security_group_id=group_name)
2417+
2418+
2419+def return_security_group_get_by_name(context, project_id, group_name):
2420+ return {'id': 1, 'name': group_name}
2421+
2422+
2423+def return_security_group_get(context, security_group_id, session):
2424+ return {'id': security_group_id}
2425+
2426+
2427+def return_instance_add_security_group(context, instance_id,
2428+ security_group_id):
2429+ pass
2430+
2431+>>>>>>> MERGE-SOURCE
2432
2433 class CreateserverextTest(test.TestCase):
2434
2435
2436=== modified file 'nova/tests/api/openstack/fakes.py'
2437--- nova/tests/api/openstack/fakes.py 2011-09-12 15:00:09 +0000
2438+++ nova/tests/api/openstack/fakes.py 2011-09-17 02:16:44 +0000
2439@@ -40,8 +40,8 @@
2440 from nova.auth.manager import User, Project
2441 import nova.image.fake
2442 from nova.image import glance
2443-from nova.image import service
2444 from nova.tests import fake_flags
2445+from nova.tests.glance import stubs as glance_stubs
2446
2447
2448 class Context(object):
2449@@ -83,7 +83,7 @@
2450 if fake_auth_context is not None:
2451 ctxt = fake_auth_context
2452 else:
2453- ctxt = context.RequestContext('fake', 'fake')
2454+ ctxt = context.RequestContext('fake', 'fake', auth_token=True)
2455 api10 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
2456 limits.RateLimitingMiddleware(inner_app10)))
2457 api11 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
2458@@ -177,6 +177,39 @@
2459 stubs.Set(nova.compute.API, 'backup', backup)
2460
2461
2462+def _make_image_fixtures():
2463+ NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
2464+
2465+ image_id = 123
2466+ base_attrs = {'deleted': False}
2467+
2468+ fixtures = []
2469+
2470+ def add_fixture(**kwargs):
2471+ kwargs.update(base_attrs)
2472+ fixtures.append(kwargs)
2473+
2474+ # Public image
2475+ add_fixture(id=image_id, name='public image', is_public=True,
2476+ status='active', properties={'key1': 'value1'})
2477+ image_id += 1
2478+
2479+ # Snapshot for User 1
2480+ server_ref = 'http://localhost/v1.1/servers/42'
2481+ snapshot_properties = {'instance_ref': server_ref, 'user_id': 'fake'}
2482+ for status in ('queued', 'saving', 'active', 'killed',
2483+ 'deleted', 'pending_delete'):
2484+ add_fixture(id=image_id, name='%s snapshot' % status,
2485+ is_public=False, status=status,
2486+ properties=snapshot_properties)
2487+ image_id += 1
2488+
2489+ # Image without a name
2490+ add_fixture(id=image_id, is_public=True, status='active', properties={})
2491+
2492+ return fixtures
2493+
2494+
2495 def stub_out_glance_add_image(stubs, sent_to_glance):
2496 """
2497 We return the metadata sent to glance by modifying the sent_to_glance dict
2498@@ -192,91 +225,11 @@
2499 stubs.Set(glance_client.Client, 'add_image', fake_add_image)
2500
2501
2502-def stub_out_glance(stubs, initial_fixtures=None):
2503-
2504- class FakeGlanceClient:
2505-
2506- def __init__(self, initial_fixtures):
2507- self.fixtures = initial_fixtures or []
2508-
2509- def _filter_images(self, filters=None, marker=None, limit=None):
2510- found = True
2511- if marker:
2512- found = False
2513- if limit == 0:
2514- limit = None
2515-
2516- fixtures = []
2517- count = 0
2518- for f in self.fixtures:
2519- if limit and count >= limit:
2520- break
2521- if found:
2522- fixtures.append(f)
2523- count = count + 1
2524- if f['id'] == marker:
2525- found = True
2526-
2527- return fixtures
2528-
2529- def fake_get_images(self, filters=None, marker=None, limit=None):
2530- fixtures = self._filter_images(filters, marker, limit)
2531- return [dict(id=f['id'], name=f['name'])
2532- for f in fixtures]
2533-
2534- def fake_get_images_detailed(self, filters=None,
2535- marker=None, limit=None):
2536- return self._filter_images(filters, marker, limit)
2537-
2538- def fake_get_image_meta(self, image_id):
2539- image = self._find_image(image_id)
2540- if image:
2541- return copy.deepcopy(image)
2542- raise glance_exc.NotFound
2543-
2544- def fake_add_image(self, image_meta, data=None):
2545- image_meta = copy.deepcopy(image_meta)
2546- image_id = ''.join(random.choice(string.letters)
2547- for _ in range(20))
2548- image_meta['id'] = image_id
2549- self.fixtures.append(image_meta)
2550- return copy.deepcopy(image_meta)
2551-
2552- def fake_update_image(self, image_id, image_meta, data=None):
2553- for attr in ('created_at', 'updated_at', 'deleted_at', 'deleted'):
2554- if attr in image_meta:
2555- del image_meta[attr]
2556-
2557- f = self._find_image(image_id)
2558- if not f:
2559- raise glance_exc.NotFound
2560-
2561- f.update(image_meta)
2562- return copy.deepcopy(f)
2563-
2564- def fake_delete_image(self, image_id):
2565- f = self._find_image(image_id)
2566- if not f:
2567- raise glance_exc.NotFound
2568-
2569- self.fixtures.remove(f)
2570-
2571- def _find_image(self, image_id):
2572- for f in self.fixtures:
2573- if str(f['id']) == str(image_id):
2574- return f
2575- return None
2576-
2577- GlanceClient = glance_client.Client
2578- fake = FakeGlanceClient(initial_fixtures)
2579-
2580- stubs.Set(GlanceClient, 'get_images', fake.fake_get_images)
2581- stubs.Set(GlanceClient, 'get_images_detailed',
2582- fake.fake_get_images_detailed)
2583- stubs.Set(GlanceClient, 'get_image_meta', fake.fake_get_image_meta)
2584- stubs.Set(GlanceClient, 'add_image', fake.fake_add_image)
2585- stubs.Set(GlanceClient, 'update_image', fake.fake_update_image)
2586- stubs.Set(GlanceClient, 'delete_image', fake.fake_delete_image)
2587+def stub_out_glance(stubs):
2588+ def fake_get_image_service():
2589+ client = glance_stubs.StubGlanceClient(_make_image_fixtures())
2590+ return nova.image.glance.GlanceImageService(client)
2591+ stubs.Set(nova.image, 'get_default_image_service', fake_get_image_service)
2592
2593
2594 class FakeToken(object):
2595
2596=== modified file 'nova/tests/api/openstack/test_api.py'
2597--- nova/tests/api/openstack/test_api.py 2011-06-14 14:16:51 +0000
2598+++ nova/tests/api/openstack/test_api.py 2011-09-17 02:16:44 +0000
2599@@ -20,6 +20,7 @@
2600 import webob.exc
2601 import webob.dec
2602
2603+from lxml import etree
2604 from webob import Request
2605
2606 from nova import test
2607@@ -52,6 +53,30 @@
2608 res = req.get_response(fakes.wsgi_app())
2609 self.assertEqual(res.status_int, 400)
2610
2611+ def test_vendor_content_type_json(self):
2612+ ctype = 'application/vnd.openstack.compute+json'
2613+
2614+ req = webob.Request.blank('/')
2615+ req.headers['Accept'] = ctype
2616+
2617+ res = req.get_response(fakes.wsgi_app())
2618+ self.assertEqual(res.status_int, 200)
2619+ self.assertEqual(res.content_type, ctype)
2620+
2621+ body = json.loads(res.body)
2622+
2623+ def test_vendor_content_type_xml(self):
2624+ ctype = 'application/vnd.openstack.compute+xml'
2625+
2626+ req = webob.Request.blank('/')
2627+ req.headers['Accept'] = ctype
2628+
2629+ res = req.get_response(fakes.wsgi_app())
2630+ self.assertEqual(res.status_int, 200)
2631+ self.assertEqual(res.content_type, ctype)
2632+
2633+ body = etree.XML(res.body)
2634+
2635 def test_exceptions_are_converted_to_faults(self):
2636
2637 @webob.dec.wsgify
2638
2639=== modified file 'nova/tests/api/openstack/test_common.py'
2640--- nova/tests/api/openstack/test_common.py 2011-08-11 19:30:43 +0000
2641+++ nova/tests/api/openstack/test_common.py 2011-09-17 02:16:44 +0000
2642@@ -19,6 +19,7 @@
2643 Test suites for 'common' code used throughout the OpenStack HTTP API.
2644 """
2645
2646+from lxml import etree
2647 import webob.exc
2648 import xml.dom.minidom as minidom
2649
2650@@ -26,6 +27,11 @@
2651
2652 from nova import test
2653 from nova.api.openstack import common
2654+from nova.api.openstack import xmlutil
2655+
2656+
2657+NS = "{http://docs.openstack.org/compute/api/v1.1}"
2658+ATOMNS = "{http://www.w3.org/2005/Atom}"
2659
2660
2661 class LimiterTest(test.TestCase):
2662@@ -237,21 +243,41 @@
2663 common.remove_version_from_href,
2664 fixture)
2665
2666- def test_get_id_from_href(self):
2667+ def test_get_id_from_href_with_int_url(self):
2668 fixture = 'http://www.testsite.com/dir/45'
2669 actual = common.get_id_from_href(fixture)
2670- expected = 45
2671- self.assertEqual(actual, expected)
2672-
2673- def test_get_id_from_href_bad_request(self):
2674- fixture = 'http://45'
2675- self.assertRaises(ValueError,
2676- common.get_id_from_href,
2677- fixture)
2678-
2679- def test_get_id_from_href_int(self):
2680- fixture = 1
2681- self.assertEqual(fixture, common.get_id_from_href(fixture))
2682+ expected = '45'
2683+ self.assertEqual(actual, expected)
2684+
2685+ def test_get_id_from_href_with_int(self):
2686+ fixture = '45'
2687+ actual = common.get_id_from_href(fixture)
2688+ expected = '45'
2689+ self.assertEqual(actual, expected)
2690+
2691+ def test_get_id_from_href_with_int_url_query(self):
2692+ fixture = 'http://www.testsite.com/dir/45?asdf=jkl'
2693+ actual = common.get_id_from_href(fixture)
2694+ expected = '45'
2695+ self.assertEqual(actual, expected)
2696+
2697+ def test_get_id_from_href_with_uuid_url(self):
2698+ fixture = 'http://www.testsite.com/dir/abc123'
2699+ actual = common.get_id_from_href(fixture)
2700+ expected = "abc123"
2701+ self.assertEqual(actual, expected)
2702+
2703+ def test_get_id_from_href_with_uuid_url_query(self):
2704+ fixture = 'http://www.testsite.com/dir/abc123?asdf=jkl'
2705+ actual = common.get_id_from_href(fixture)
2706+ expected = "abc123"
2707+ self.assertEqual(actual, expected)
2708+
2709+ def test_get_id_from_href_with_uuid(self):
2710+ fixture = 'abc123'
2711+ actual = common.get_id_from_href(fixture)
2712+ expected = 'abc123'
2713+ self.assertEqual(actual, expected)
2714
2715 def test_get_version_from_href(self):
2716 fixture = 'http://www.testsite.com/v1.1/images'
2717@@ -314,6 +340,20 @@
2718
2719 class MetadataXMLSerializationTest(test.TestCase):
2720
2721+ def test_xml_declaration(self):
2722+ serializer = common.MetadataXMLSerializer()
2723+ fixture = {
2724+ 'metadata': {
2725+ 'one': 'two',
2726+ 'three': 'four',
2727+ },
2728+ }
2729+
2730+ output = serializer.serialize(fixture, 'index')
2731+ print output
2732+ has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>")
2733+ self.assertTrue(has_dec)
2734+
2735 def test_index(self):
2736 serializer = common.MetadataXMLSerializer()
2737 fixture = {
2738@@ -323,16 +363,16 @@
2739 },
2740 }
2741 output = serializer.serialize(fixture, 'index')
2742- actual = minidom.parseString(output.replace(" ", ""))
2743-
2744- expected = minidom.parseString("""
2745- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
2746- <meta key="three">four</meta>
2747- <meta key="one">two</meta>
2748- </metadata>
2749- """.replace(" ", "").replace("\n", ""))
2750-
2751- self.assertEqual(expected.toxml(), actual.toxml())
2752+ print output
2753+ root = etree.XML(output)
2754+ xmlutil.validate_schema(root, 'metadata')
2755+ metadata_dict = fixture['metadata']
2756+ metadata_elems = root.findall('{0}meta'.format(NS))
2757+ self.assertEqual(len(metadata_elems), 2)
2758+ for i, metadata_elem in enumerate(metadata_elems):
2759+ (meta_key, meta_value) = metadata_dict.items()[i]
2760+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
2761+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
2762
2763 def test_index_null(self):
2764 serializer = common.MetadataXMLSerializer()
2765@@ -342,15 +382,16 @@
2766 },
2767 }
2768 output = serializer.serialize(fixture, 'index')
2769- actual = minidom.parseString(output.replace(" ", ""))
2770-
2771- expected = minidom.parseString("""
2772- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
2773- <meta key="None">None</meta>
2774- </metadata>
2775- """.replace(" ", "").replace("\n", ""))
2776-
2777- self.assertEqual(expected.toxml(), actual.toxml())
2778+ print output
2779+ root = etree.XML(output)
2780+ xmlutil.validate_schema(root, 'metadata')
2781+ metadata_dict = fixture['metadata']
2782+ metadata_elems = root.findall('{0}meta'.format(NS))
2783+ self.assertEqual(len(metadata_elems), 1)
2784+ for i, metadata_elem in enumerate(metadata_elems):
2785+ (meta_key, meta_value) = metadata_dict.items()[i]
2786+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
2787+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
2788
2789 def test_index_unicode(self):
2790 serializer = common.MetadataXMLSerializer()
2791@@ -360,15 +401,16 @@
2792 },
2793 }
2794 output = serializer.serialize(fixture, 'index')
2795- actual = minidom.parseString(output.replace(" ", ""))
2796-
2797- expected = minidom.parseString(u"""
2798- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
2799- <meta key="three">Jos\xe9</meta>
2800- </metadata>
2801- """.encode("UTF-8").replace(" ", "").replace("\n", ""))
2802-
2803- self.assertEqual(expected.toxml(), actual.toxml())
2804+ print output
2805+ root = etree.XML(output)
2806+ xmlutil.validate_schema(root, 'metadata')
2807+ metadata_dict = fixture['metadata']
2808+ metadata_elems = root.findall('{0}meta'.format(NS))
2809+ self.assertEqual(len(metadata_elems), 1)
2810+ for i, metadata_elem in enumerate(metadata_elems):
2811+ (meta_key, meta_value) = metadata_dict.items()[i]
2812+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
2813+ self.assertEqual(metadata_elem.text.strip(), meta_value)
2814
2815 def test_show(self):
2816 serializer = common.MetadataXMLSerializer()
2817@@ -378,14 +420,12 @@
2818 },
2819 }
2820 output = serializer.serialize(fixture, 'show')
2821- actual = minidom.parseString(output.replace(" ", ""))
2822-
2823- expected = minidom.parseString("""
2824- <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
2825- key="one">two</meta>
2826- """.replace(" ", "").replace("\n", ""))
2827-
2828- self.assertEqual(expected.toxml(), actual.toxml())
2829+ print output
2830+ root = etree.XML(output)
2831+ meta_dict = fixture['meta']
2832+ (meta_key, meta_value) = meta_dict.items()[0]
2833+ self.assertEqual(str(root.get('key')), str(meta_key))
2834+ self.assertEqual(root.text.strip(), meta_value)
2835
2836 def test_update_all(self):
2837 serializer = common.MetadataXMLSerializer()
2838@@ -396,16 +436,16 @@
2839 },
2840 }
2841 output = serializer.serialize(fixture, 'update_all')
2842- actual = minidom.parseString(output.replace(" ", ""))
2843-
2844- expected = minidom.parseString("""
2845- <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
2846- <meta key="key6">value6</meta>
2847- <meta key="key4">value4</meta>
2848- </metadata>
2849- """.replace(" ", "").replace("\n", ""))
2850-
2851- self.assertEqual(expected.toxml(), actual.toxml())
2852+ print output
2853+ root = etree.XML(output)
2854+ xmlutil.validate_schema(root, 'metadata')
2855+ metadata_dict = fixture['metadata']
2856+ metadata_elems = root.findall('{0}meta'.format(NS))
2857+ self.assertEqual(len(metadata_elems), 2)
2858+ for i, metadata_elem in enumerate(metadata_elems):
2859+ (meta_key, meta_value) = metadata_dict.items()[i]
2860+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
2861+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
2862
2863 def test_update_item(self):
2864 serializer = common.MetadataXMLSerializer()
2865@@ -415,14 +455,12 @@
2866 },
2867 }
2868 output = serializer.serialize(fixture, 'update')
2869- actual = minidom.parseString(output.replace(" ", ""))
2870-
2871- expected = minidom.parseString("""
2872- <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
2873- key="one">two</meta>
2874- """.replace(" ", "").replace("\n", ""))
2875-
2876- self.assertEqual(expected.toxml(), actual.toxml())
2877+ print output
2878+ root = etree.XML(output)
2879+ meta_dict = fixture['meta']
2880+ (meta_key, meta_value) = meta_dict.items()[0]
2881+ self.assertEqual(str(root.get('key')), str(meta_key))
2882+ self.assertEqual(root.text.strip(), meta_value)
2883
2884 def test_create(self):
2885 serializer = common.MetadataXMLSerializer()
2886@@ -434,6 +472,16 @@
2887 },
2888 }
2889 output = serializer.serialize(fixture, 'create')
2890+ print output
2891+ root = etree.XML(output)
2892+ xmlutil.validate_schema(root, 'metadata')
2893+ metadata_dict = fixture['metadata']
2894+ metadata_elems = root.findall('{0}meta'.format(NS))
2895+ self.assertEqual(len(metadata_elems), 3)
2896+ for i, metadata_elem in enumerate(metadata_elems):
2897+ (meta_key, meta_value) = metadata_dict.items()[i]
2898+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
2899+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
2900 actual = minidom.parseString(output.replace(" ", ""))
2901
2902 expected = minidom.parseString("""
2903
2904=== modified file 'nova/tests/api/openstack/test_extensions.py'
2905--- nova/tests/api/openstack/test_extensions.py 2011-08-30 19:41:30 +0000
2906+++ nova/tests/api/openstack/test_extensions.py 2011-09-17 02:16:44 +0000
2907@@ -87,6 +87,7 @@
2908 self.ext_list = [
2909 "Createserverext",
2910 "FlavorExtraSpecs",
2911+ "FlavorExtraData",
2912 "Floating_ips",
2913 "Fox In Socks",
2914 "Hosts",
2915
2916=== modified file 'nova/tests/api/openstack/test_flavors.py'
2917--- nova/tests/api/openstack/test_flavors.py 2011-08-11 18:40:05 +0000
2918+++ nova/tests/api/openstack/test_flavors.py 2011-09-17 02:16:44 +0000
2919@@ -17,16 +17,21 @@
2920
2921 import json
2922 import webob
2923-import xml.dom.minidom as minidom
2924+from lxml import etree
2925
2926 from nova.api.openstack import flavors
2927 import nova.db.api
2928 from nova import exception
2929 from nova import test
2930+from nova.api.openstack import xmlutil
2931 from nova.tests.api.openstack import fakes
2932 from nova import wsgi
2933
2934
2935+NS = "{http://docs.openstack.org/compute/api/v1.1}"
2936+ATOMNS = "{http://www.w3.org/2005/Atom}"
2937+
2938+
2939 def stub_flavor(flavorid, name, memory_mb="256", local_gb="10"):
2940 return {
2941 "flavorid": str(flavorid),
2942@@ -107,12 +112,20 @@
2943 "name": "flavor 1",
2944 "ram": "256",
2945 "disk": "10",
2946+ "rxtx_cap": "",
2947+ "rxtx_quota": "",
2948+ "swap": "",
2949+ "vcpus": "",
2950 },
2951 {
2952 "id": "2",
2953 "name": "flavor 2",
2954 "ram": "256",
2955 "disk": "10",
2956+ "rxtx_cap": "",
2957+ "rxtx_quota": "",
2958+ "swap": "",
2959+ "vcpus": "",
2960 },
2961 ]
2962 self.assertEqual(flavors, expected)
2963@@ -127,6 +140,10 @@
2964 "name": "flavor 12",
2965 "ram": "256",
2966 "disk": "10",
2967+ "rxtx_cap": "",
2968+ "rxtx_quota": "",
2969+ "swap": "",
2970+ "vcpus": "",
2971 }
2972 self.assertEqual(flavor, expected)
2973
2974@@ -149,6 +166,10 @@
2975 "name": "flavor 12",
2976 "ram": "256",
2977 "disk": "10",
2978+ "rxtx_cap": "",
2979+ "rxtx_quota": "",
2980+ "swap": "",
2981+ "vcpus": "",
2982 "links": [
2983 {
2984 "rel": "self",
2985@@ -216,6 +237,10 @@
2986 "name": "flavor 1",
2987 "ram": "256",
2988 "disk": "10",
2989+ "rxtx_cap": "",
2990+ "rxtx_quota": "",
2991+ "swap": "",
2992+ "vcpus": "",
2993 "links": [
2994 {
2995 "rel": "self",
2996@@ -232,6 +257,10 @@
2997 "name": "flavor 2",
2998 "ram": "256",
2999 "disk": "10",
3000+ "rxtx_cap": "",
3001+ "rxtx_quota": "",
3002+ "swap": "",
3003+ "vcpus": "",
3004 "links": [
3005 {
3006 "rel": "self",
3007@@ -262,15 +291,50 @@
3008
3009 class FlavorsXMLSerializationTest(test.TestCase):
3010
3011+ def test_xml_declaration(self):
3012+ serializer = flavors.FlavorXMLSerializer()
3013+
3014+ fixture = {
3015+ "flavor": {
3016+ "id": "12",
3017+ "name": "asdf",
3018+ "ram": "256",
3019+ "disk": "10",
3020+ "rxtx_cap": "",
3021+ "rxtx_quota": "",
3022+ "swap": "",
3023+ "vcpus": "",
3024+ "links": [
3025+ {
3026+ "rel": "self",
3027+ "href": "http://localhost/v1.1/fake/flavors/12",
3028+ },
3029+ {
3030+ "rel": "bookmark",
3031+ "href": "http://localhost/fake/flavors/12",
3032+ },
3033+ ],
3034+ },
3035+ }
3036+
3037+ output = serializer.serialize(fixture, 'show')
3038+ print output
3039+ has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>")
3040+ self.assertTrue(has_dec)
3041+
3042 def test_show(self):
3043 serializer = flavors.FlavorXMLSerializer()
3044
3045- input = {
3046+ fixture = {
3047 "flavor": {
3048 "id": "12",
3049 "name": "asdf",
3050 "ram": "256",
3051 "disk": "10",
3052+ "rxtx_cap": "",
3053+ "rxtx_quota": "",
3054+ "swap": "",
3055+ "vcpus": "",
3056 "links": [
3057 {
3058 "rel": "self",
3059@@ -284,34 +348,34 @@
3060 },
3061 }
3062
3063- output = serializer.serialize(input, 'show')
3064- actual = minidom.parseString(output.replace(" ", ""))
3065-
3066- expected = minidom.parseString("""
3067- <flavor xmlns="http://docs.openstack.org/compute/api/v1.1"
3068- xmlns:atom="http://www.w3.org/2005/Atom"
3069- id="12"
3070- name="asdf"
3071- ram="256"
3072- disk="10">
3073- <atom:link href="http://localhost/v1.1/fake/flavors/12"
3074- rel="self"/>
3075- <atom:link href="http://localhost/fake/flavors/12"
3076- rel="bookmark"/>
3077- </flavor>
3078- """.replace(" ", ""))
3079-
3080- self.assertEqual(expected.toxml(), actual.toxml())
3081+ output = serializer.serialize(fixture, 'show')
3082+ print output
3083+ root = etree.XML(output)
3084+ xmlutil.validate_schema(root, 'flavor')
3085+ flavor_dict = fixture['flavor']
3086+
3087+ for key in ['name', 'id', 'ram', 'disk']:
3088+ self.assertEqual(root.get(key), str(flavor_dict[key]))
3089+
3090+ link_nodes = root.findall('{0}link'.format(ATOMNS))
3091+ self.assertEqual(len(link_nodes), 2)
3092+ for i, link in enumerate(flavor_dict['links']):
3093+ for key, value in link.items():
3094+ self.assertEqual(link_nodes[i].get(key), value)
3095
3096 def test_show_handles_integers(self):
3097 serializer = flavors.FlavorXMLSerializer()
3098
3099- input = {
3100+ fixture = {
3101 "flavor": {
3102 "id": 12,
3103 "name": "asdf",
3104 "ram": 256,
3105 "disk": 10,
3106+ "rxtx_cap": "",
3107+ "rxtx_quota": "",
3108+ "swap": "",
3109+ "vcpus": "",
3110 "links": [
3111 {
3112 "rel": "self",
3113@@ -325,35 +389,35 @@
3114 },
3115 }
3116
3117- output = serializer.serialize(input, 'show')
3118- actual = minidom.parseString(output.replace(" ", ""))
3119-
3120- expected = minidom.parseString("""
3121- <flavor xmlns="http://docs.openstack.org/compute/api/v1.1"
3122- xmlns:atom="http://www.w3.org/2005/Atom"
3123- id="12"
3124- name="asdf"
3125- ram="256"
3126- disk="10">
3127- <atom:link href="http://localhost/v1.1/fake/flavors/12"
3128- rel="self"/>
3129- <atom:link href="http://localhost/fake/flavors/12"
3130- rel="bookmark"/>
3131- </flavor>
3132- """.replace(" ", ""))
3133-
3134- self.assertEqual(expected.toxml(), actual.toxml())
3135+ output = serializer.serialize(fixture, 'show')
3136+ print output
3137+ root = etree.XML(output)
3138+ xmlutil.validate_schema(root, 'flavor')
3139+ flavor_dict = fixture['flavor']
3140+
3141+ for key in ['name', 'id', 'ram', 'disk']:
3142+ self.assertEqual(root.get(key), str(flavor_dict[key]))
3143+
3144+ link_nodes = root.findall('{0}link'.format(ATOMNS))
3145+ self.assertEqual(len(link_nodes), 2)
3146+ for i, link in enumerate(flavor_dict['links']):
3147+ for key, value in link.items():
3148+ self.assertEqual(link_nodes[i].get(key), value)
3149
3150 def test_detail(self):
3151 serializer = flavors.FlavorXMLSerializer()
3152
3153- input = {
3154+ fixture = {
3155 "flavors": [
3156 {
3157 "id": "23",
3158 "name": "flavor 23",
3159 "ram": "512",
3160 "disk": "20",
3161+ "rxtx_cap": "",
3162+ "rxtx_quota": "",
3163+ "swap": "",
3164+ "vcpus": "",
3165 "links": [
3166 {
3167 "rel": "self",
3168@@ -369,6 +433,10 @@
3169 "name": "flavor 13",
3170 "ram": "256",
3171 "disk": "10",
3172+ "rxtx_cap": "",
3173+ "rxtx_quota": "",
3174+ "swap": "",
3175+ "vcpus": "",
3176 "links": [
3177 {
3178 "rel": "self",
3179@@ -383,45 +451,38 @@
3180 ],
3181 }
3182
3183- output = serializer.serialize(input, 'detail')
3184- actual = minidom.parseString(output.replace(" ", ""))
3185-
3186- expected = minidom.parseString("""
3187- <flavors xmlns="http://docs.openstack.org/compute/api/v1.1"
3188- xmlns:atom="http://www.w3.org/2005/Atom">
3189- <flavor id="23"
3190- name="flavor 23"
3191- ram="512"
3192- disk="20">
3193- <atom:link href="http://localhost/v1.1/fake/flavors/23"
3194- rel="self"/>
3195- <atom:link href="http://localhost/fake/flavors/23"
3196- rel="bookmark"/>
3197- </flavor>
3198- <flavor id="13"
3199- name="flavor 13"
3200- ram="256"
3201- disk="10">
3202- <atom:link href="http://localhost/v1.1/fake/flavors/13"
3203- rel="self"/>
3204- <atom:link href="http://localhost/fake/flavors/13"
3205- rel="bookmark"/>
3206- </flavor>
3207- </flavors>
3208- """.replace(" ", "") % locals())
3209-
3210- self.assertEqual(expected.toxml(), actual.toxml())
3211+ output = serializer.serialize(fixture, 'detail')
3212+ print output
3213+ root = etree.XML(output)
3214+ xmlutil.validate_schema(root, 'flavors')
3215+ flavor_elems = root.findall('{0}flavor'.format(NS))
3216+ self.assertEqual(len(flavor_elems), 2)
3217+ for i, flavor_elem in enumerate(flavor_elems):
3218+ flavor_dict = fixture['flavors'][i]
3219+
3220+ for key in ['name', 'id', 'ram', 'disk']:
3221+ self.assertEqual(flavor_elem.get(key), str(flavor_dict[key]))
3222+
3223+ link_nodes = flavor_elem.findall('{0}link'.format(ATOMNS))
3224+ self.assertEqual(len(link_nodes), 2)
3225+ for i, link in enumerate(flavor_dict['links']):
3226+ for key, value in link.items():
3227+ self.assertEqual(link_nodes[i].get(key), value)
3228
3229 def test_index(self):
3230 serializer = flavors.FlavorXMLSerializer()
3231
3232- input = {
3233+ fixture = {
3234 "flavors": [
3235 {
3236 "id": "23",
3237 "name": "flavor 23",
3238 "ram": "512",
3239 "disk": "20",
3240+ "rxtx_cap": "",
3241+ "rxtx_quota": "",
3242+ "swap": "",
3243+ "vcpus": "",
3244 "links": [
3245 {
3246 "rel": "self",
3247@@ -437,6 +498,10 @@
3248 "name": "flavor 13",
3249 "ram": "256",
3250 "disk": "10",
3251+ "rxtx_cap": "",
3252+ "rxtx_quota": "",
3253+ "swap": "",
3254+ "vcpus": "",
3255 "links": [
3256 {
3257 "rel": "self",
3258@@ -451,42 +516,34 @@
3259 ],
3260 }
3261
3262- output = serializer.serialize(input, 'index')
3263- actual = minidom.parseString(output.replace(" ", ""))
3264-
3265- expected = minidom.parseString("""
3266- <flavors xmlns="http://docs.openstack.org/compute/api/v1.1"
3267- xmlns:atom="http://www.w3.org/2005/Atom">
3268- <flavor id="23" name="flavor 23">
3269- <atom:link href="http://localhost/v1.1/fake/flavors/23"
3270- rel="self"/>
3271- <atom:link href="http://localhost/fake/flavors/23"
3272- rel="bookmark"/>
3273- </flavor>
3274- <flavor id="13" name="flavor 13">
3275- <atom:link href="http://localhost/v1.1/fake/flavors/13"
3276- rel="self"/>
3277- <atom:link href="http://localhost/fake/flavors/13"
3278- rel="bookmark"/>
3279- </flavor>
3280- </flavors>
3281- """.replace(" ", "") % locals())
3282-
3283- self.assertEqual(expected.toxml(), actual.toxml())
3284+ output = serializer.serialize(fixture, 'index')
3285+ print output
3286+ root = etree.XML(output)
3287+ xmlutil.validate_schema(root, 'flavors_index')
3288+ flavor_elems = root.findall('{0}flavor'.format(NS))
3289+ self.assertEqual(len(flavor_elems), 2)
3290+ for i, flavor_elem in enumerate(flavor_elems):
3291+ flavor_dict = fixture['flavors'][i]
3292+
3293+ for key in ['name', 'id']:
3294+ self.assertEqual(flavor_elem.get(key), str(flavor_dict[key]))
3295+
3296+ link_nodes = flavor_elem.findall('{0}link'.format(ATOMNS))
3297+ self.assertEqual(len(link_nodes), 2)
3298+ for i, link in enumerate(flavor_dict['links']):
3299+ for key, value in link.items():
3300+ self.assertEqual(link_nodes[i].get(key), value)
3301
3302 def test_index_empty(self):
3303 serializer = flavors.FlavorXMLSerializer()
3304
3305- input = {
3306+ fixture = {
3307 "flavors": [],
3308 }
3309
3310- output = serializer.serialize(input, 'index')
3311- actual = minidom.parseString(output.replace(" ", ""))
3312-
3313- expected = minidom.parseString("""
3314- <flavors xmlns="http://docs.openstack.org/compute/api/v1.1"
3315- xmlns:atom="http://www.w3.org/2005/Atom" />
3316- """.replace(" ", "") % locals())
3317-
3318- self.assertEqual(expected.toxml(), actual.toxml())
3319+ output = serializer.serialize(fixture, 'index')
3320+ print output
3321+ root = etree.XML(output)
3322+ xmlutil.validate_schema(root, 'flavors_index')
3323+ flavor_elems = root.findall('{0}flavor'.format(NS))
3324+ self.assertEqual(len(flavor_elems), 0)
3325
3326=== modified file 'nova/tests/api/openstack/test_image_metadata.py'
3327--- nova/tests/api/openstack/test_image_metadata.py 2011-08-19 02:24:31 +0000
3328+++ nova/tests/api/openstack/test_image_metadata.py 2011-09-17 02:16:44 +0000
3329@@ -23,7 +23,6 @@
3330 from nova.api import openstack
3331 from nova import test
3332 from nova.tests.api.openstack import fakes
3333-import nova.wsgi
3334
3335
3336 FLAGS = flags.FLAGS
3337@@ -31,76 +30,20 @@
3338
3339 class ImageMetaDataTest(test.TestCase):
3340
3341- IMAGE_FIXTURES = [
3342- {'status': 'active',
3343- 'name': 'image1',
3344- 'deleted': False,
3345- 'container_format': None,
3346- 'checksum': None,
3347- 'created_at': '2011-03-22T17:40:15',
3348- 'disk_format': None,
3349- 'updated_at': '2011-03-22T17:40:15',
3350- 'id': '1',
3351- 'location': 'file:///var/lib/glance/images/1',
3352- 'is_public': True,
3353- 'deleted_at': None,
3354- 'properties': {
3355- 'key1': 'value1',
3356- 'key2': 'value2'},
3357- 'size': 5882349},
3358- {'status': 'active',
3359- 'name': 'image2',
3360- 'deleted': False,
3361- 'container_format': None,
3362- 'checksum': None,
3363- 'created_at': '2011-03-22T17:40:15',
3364- 'disk_format': None,
3365- 'updated_at': '2011-03-22T17:40:15',
3366- 'id': '2',
3367- 'location': 'file:///var/lib/glance/images/2',
3368- 'is_public': True,
3369- 'deleted_at': None,
3370- 'properties': {
3371- 'key1': 'value1',
3372- 'key2': 'value2'},
3373- 'size': 5882349},
3374- {'status': 'active',
3375- 'name': 'image3',
3376- 'deleted': False,
3377- 'container_format': None,
3378- 'checksum': None,
3379- 'created_at': '2011-03-22T17:40:15',
3380- 'disk_format': None,
3381- 'updated_at': '2011-03-22T17:40:15',
3382- 'id': '3',
3383- 'location': 'file:///var/lib/glance/images/2',
3384- 'is_public': True,
3385- 'deleted_at': None,
3386- 'properties': {},
3387- 'size': 5882349},
3388- ]
3389-
3390 def setUp(self):
3391 super(ImageMetaDataTest, self).setUp()
3392- self.flags(image_service='nova.image.glance.GlanceImageService')
3393- # NOTE(dprince) max out properties/metadata in image 3 for testing
3394- img3 = self.IMAGE_FIXTURES[2]
3395- for num in range(FLAGS.quota_metadata_items):
3396- img3['properties']['key%i' % num] = "blah"
3397- fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES)
3398+ fakes.stub_out_glance(self.stubs)
3399
3400 def test_index(self):
3401- req = webob.Request.blank('/v1.1/123/images/1/metadata')
3402+ req = webob.Request.blank('/v1.1/123/images/123/metadata')
3403 res = req.get_response(fakes.wsgi_app())
3404 res_dict = json.loads(res.body)
3405 self.assertEqual(200, res.status_int)
3406- expected = self.IMAGE_FIXTURES[0]['properties']
3407- self.assertEqual(len(expected), len(res_dict['metadata']))
3408- for (key, value) in res_dict['metadata'].items():
3409- self.assertEqual(value, res_dict['metadata'][key])
3410+ expected = {'metadata': {'key1': 'value1'}}
3411+ self.assertEqual(res_dict, expected)
3412
3413 def test_show(self):
3414- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
3415+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
3416 res = req.get_response(fakes.wsgi_app())
3417 res_dict = json.loads(res.body)
3418 self.assertEqual(200, res.status_int)
3419@@ -109,32 +52,38 @@
3420 self.assertEqual('value1', res_dict['meta']['key1'])
3421
3422 def test_show_not_found(self):
3423- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key9')
3424+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key9')
3425+ res = req.get_response(fakes.wsgi_app())
3426+ self.assertEqual(404, res.status_int)
3427+
3428+ def test_show_image_not_found(self):
3429+ req = webob.Request.blank('/v1.1/fake/images/100/metadata/key1')
3430 res = req.get_response(fakes.wsgi_app())
3431 self.assertEqual(404, res.status_int)
3432
3433 def test_create(self):
3434- req = webob.Request.blank('/v1.1/fake/images/2/metadata')
3435+ req = webob.Request.blank('/v1.1/fake/images/123/metadata')
3436 req.method = 'POST'
3437- req.body = '{"metadata": {"key9": "value9"}}'
3438+ req.body = '{"metadata": {"key7": "value7"}}'
3439 req.headers["content-type"] = "application/json"
3440 res = req.get_response(fakes.wsgi_app())
3441
3442 self.assertEqual(200, res.status_int)
3443 actual_output = json.loads(res.body)
3444-
3445- expected_output = {
3446- 'metadata': {
3447- 'key1': 'value1',
3448- 'key2': 'value2',
3449- 'key9': 'value9',
3450- },
3451- }
3452-
3453+ expected_output = {'metadata': {'key1': 'value1', 'key7': 'value7'}}
3454 self.assertEqual(expected_output, actual_output)
3455
3456+ def test_create_image_not_found(self):
3457+ req = webob.Request.blank('/v1.1/fake/images/100/metadata')
3458+ req.method = 'POST'
3459+ req.body = '{"metadata": {"key7": "value7"}}'
3460+ req.headers["content-type"] = "application/json"
3461+ res = req.get_response(fakes.wsgi_app())
3462+
3463+ self.assertEqual(404, res.status_int)
3464+
3465 def test_update_all(self):
3466- req = webob.Request.blank('/v1.1/fake/images/1/metadata')
3467+ req = webob.Request.blank('/v1.1/fake/images/123/metadata')
3468 req.method = 'PUT'
3469 req.body = '{"metadata": {"key9": "value9"}}'
3470 req.headers["content-type"] = "application/json"
3471@@ -142,17 +91,20 @@
3472
3473 self.assertEqual(200, res.status_int)
3474 actual_output = json.loads(res.body)
3475-
3476- expected_output = {
3477- 'metadata': {
3478- 'key9': 'value9',
3479- },
3480- }
3481-
3482+ expected_output = {'metadata': {'key9': 'value9'}}
3483 self.assertEqual(expected_output, actual_output)
3484
3485+ def test_update_all_image_not_found(self):
3486+ req = webob.Request.blank('/v1.1/fake/images/100/metadata')
3487+ req.method = 'PUT'
3488+ req.body = '{"metadata": {"key9": "value9"}}'
3489+ req.headers["content-type"] = "application/json"
3490+ res = req.get_response(fakes.wsgi_app())
3491+
3492+ self.assertEqual(404, res.status_int)
3493+
3494 def test_update_item(self):
3495- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
3496+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
3497 req.method = 'PUT'
3498 req.body = '{"meta": {"key1": "zz"}}'
3499 req.headers["content-type"] = "application/json"
3500@@ -160,15 +112,20 @@
3501
3502 self.assertEqual(200, res.status_int)
3503 actual_output = json.loads(res.body)
3504- expected_output = {
3505- 'meta': {
3506- 'key1': 'zz',
3507- },
3508- }
3509+ expected_output = {'meta': {'key1': 'zz'}}
3510 self.assertEqual(actual_output, expected_output)
3511
3512+ def test_update_item_image_not_found(self):
3513+ req = webob.Request.blank('/v1.1/fake/images/100/metadata/key1')
3514+ req.method = 'PUT'
3515+ req.body = '{"meta": {"key1": "zz"}}'
3516+ req.headers["content-type"] = "application/json"
3517+ res = req.get_response(fakes.wsgi_app())
3518+
3519+ self.assertEqual(404, res.status_int)
3520+
3521 def test_update_item_bad_body(self):
3522- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
3523+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
3524 req.method = 'PUT'
3525 req.body = '{"key1": "zz"}'
3526 req.headers["content-type"] = "application/json"
3527@@ -176,15 +133,18 @@
3528 self.assertEqual(400, res.status_int)
3529
3530 def test_update_item_too_many_keys(self):
3531- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
3532+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
3533 req.method = 'PUT'
3534- req.body = '{"meta": {"key1": "value1", "key2": "value2"}}'
3535+ overload = {}
3536+ for num in range(FLAGS.quota_metadata_items + 1):
3537+ overload['key%s' % num] = 'value%s' % num
3538+ req.body = json.dumps({'meta': overload})
3539 req.headers["content-type"] = "application/json"
3540 res = req.get_response(fakes.wsgi_app())
3541 self.assertEqual(400, res.status_int)
3542
3543 def test_update_item_body_uri_mismatch(self):
3544- req = webob.Request.blank('/v1.1/fake/images/1/metadata/bad')
3545+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/bad')
3546 req.method = 'PUT'
3547 req.body = '{"meta": {"key1": "value1"}}'
3548 req.headers["content-type"] = "application/json"
3549@@ -192,7 +152,7 @@
3550 self.assertEqual(400, res.status_int)
3551
3552 def test_update_item_xml(self):
3553- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
3554+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
3555 req.method = 'PUT'
3556 req.body = '<meta key="key1">five</meta>'
3557 req.headers["content-type"] = "application/xml"
3558@@ -200,22 +160,24 @@
3559
3560 self.assertEqual(200, res.status_int)
3561 actual_output = json.loads(res.body)
3562- expected_output = {
3563- 'meta': {
3564- 'key1': 'five',
3565- },
3566- }
3567+ expected_output = {'meta': {'key1': 'five'}}
3568 self.assertEqual(actual_output, expected_output)
3569
3570 def test_delete(self):
3571- req = webob.Request.blank('/v1.1/fake/images/2/metadata/key1')
3572+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
3573 req.method = 'DELETE'
3574 res = req.get_response(fakes.wsgi_app())
3575 self.assertEqual(204, res.status_int)
3576 self.assertEqual('', res.body)
3577
3578 def test_delete_not_found(self):
3579- req = webob.Request.blank('/v1.1/fake/images/2/metadata/blah')
3580+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/blah')
3581+ req.method = 'DELETE'
3582+ res = req.get_response(fakes.wsgi_app())
3583+ self.assertEqual(404, res.status_int)
3584+
3585+ def test_delete_image_not_found(self):
3586+ req = webob.Request.blank('/v1.1/fake/images/100/metadata/key1')
3587 req.method = 'DELETE'
3588 res = req.get_response(fakes.wsgi_app())
3589 self.assertEqual(404, res.status_int)
3590@@ -225,7 +187,7 @@
3591 for num in range(FLAGS.quota_metadata_items + 1):
3592 data['metadata']['key%i' % num] = "blah"
3593 json_string = str(data).replace("\'", "\"")
3594- req = webob.Request.blank('/v1.1/fake/images/2/metadata')
3595+ req = webob.Request.blank('/v1.1/fake/images/123/metadata')
3596 req.method = 'POST'
3597 req.body = json_string
3598 req.headers["content-type"] = "application/json"
3599@@ -233,7 +195,8 @@
3600 self.assertEqual(413, res.status_int)
3601
3602 def test_too_many_metadata_items_on_put(self):
3603- req = webob.Request.blank('/v1.1/fake/images/3/metadata/blah')
3604+ FLAGS.quota_metadata_items = 1
3605+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/blah')
3606 req.method = 'PUT'
3607 req.body = '{"meta": {"blah": "blah"}}'
3608 req.headers["content-type"] = "application/json"
3609
3610=== modified file 'nova/tests/api/openstack/test_images.py'
3611--- nova/tests/api/openstack/test_images.py 2011-09-16 13:12:51 +0000
3612+++ nova/tests/api/openstack/test_images.py 2011-09-17 02:16:44 +0000
3613@@ -22,340 +22,57 @@
3614
3615 import copy
3616 import json
3617-import os
3618-import shutil
3619-import tempfile
3620 import xml.dom.minidom as minidom
3621
3622+from lxml import etree
3623 import mox
3624 import stubout
3625 import webob
3626
3627-from glance import client as glance_client
3628 from nova import context
3629-from nova import exception
3630-from nova import test
3631-from nova import utils
3632 import nova.api.openstack
3633 from nova.api.openstack import images
3634+from nova.api.openstack import xmlutil
3635+from nova import test
3636 from nova.tests.api.openstack import fakes
3637
3638
3639-class _BaseImageServiceTests(test.TestCase):
3640- """Tasks to test for all image services"""
3641-
3642- def __init__(self, *args, **kwargs):
3643- super(_BaseImageServiceTests, self).__init__(*args, **kwargs)
3644- self.service = None
3645- self.context = None
3646-
3647- def test_create(self):
3648- fixture = self._make_fixture('test image')
3649- num_images = len(self.service.index(self.context))
3650-
3651- image_id = self.service.create(self.context, fixture)['id']
3652-
3653- self.assertNotEquals(None, image_id)
3654- self.assertEquals(num_images + 1,
3655- len(self.service.index(self.context)))
3656-
3657- def test_create_and_show_non_existing_image(self):
3658- fixture = self._make_fixture('test image')
3659- num_images = len(self.service.index(self.context))
3660-
3661- image_id = self.service.create(self.context, fixture)['id']
3662-
3663- self.assertNotEquals(None, image_id)
3664- self.assertRaises(exception.NotFound,
3665- self.service.show,
3666- self.context,
3667- 'bad image id')
3668-
3669- def test_create_and_show_non_existing_image_by_name(self):
3670- fixture = self._make_fixture('test image')
3671- num_images = len(self.service.index(self.context))
3672-
3673- image_id = self.service.create(self.context, fixture)['id']
3674-
3675- self.assertNotEquals(None, image_id)
3676- self.assertRaises(exception.ImageNotFound,
3677- self.service.show_by_name,
3678- self.context,
3679- 'bad image id')
3680-
3681- def test_update(self):
3682- fixture = self._make_fixture('test image')
3683- image_id = self.service.create(self.context, fixture)['id']
3684- fixture['status'] = 'in progress'
3685-
3686- self.service.update(self.context, image_id, fixture)
3687-
3688- new_image_data = self.service.show(self.context, image_id)
3689- self.assertEquals('in progress', new_image_data['status'])
3690-
3691- def test_delete(self):
3692- fixture1 = self._make_fixture('test image 1')
3693- fixture2 = self._make_fixture('test image 2')
3694- fixtures = [fixture1, fixture2]
3695-
3696- num_images = len(self.service.index(self.context))
3697- self.assertEquals(0, num_images, str(self.service.index(self.context)))
3698-
3699- ids = []
3700- for fixture in fixtures:
3701- new_id = self.service.create(self.context, fixture)['id']
3702- ids.append(new_id)
3703-
3704- num_images = len(self.service.index(self.context))
3705- self.assertEquals(2, num_images, str(self.service.index(self.context)))
3706-
3707- self.service.delete(self.context, ids[0])
3708-
3709- num_images = len(self.service.index(self.context))
3710- self.assertEquals(1, num_images)
3711-
3712- def test_index(self):
3713- fixture = self._make_fixture('test image')
3714- image_id = self.service.create(self.context, fixture)['id']
3715- image_metas = self.service.index(self.context)
3716- expected = [{'id': 'DONTCARE', 'name': 'test image'}]
3717- self.assertDictListMatch(image_metas, expected)
3718-
3719- @staticmethod
3720- def _make_fixture(name):
3721- fixture = {'name': name,
3722- 'updated': None,
3723- 'created': None,
3724- 'status': None,
3725- 'is_public': True}
3726- return fixture
3727-
3728-
3729-class GlanceImageServiceTest(_BaseImageServiceTests):
3730-
3731- """Tests the Glance image service, in particular that metadata translation
3732- works properly.
3733-
3734- At a high level, the translations involved are:
3735-
3736- 1. Glance -> ImageService - This is needed so we can support
3737- multple ImageServices (Glance, Local, etc)
3738-
3739- 2. ImageService -> API - This is needed so we can support multple
3740- APIs (OpenStack, EC2)
3741- """
3742- def setUp(self):
3743- super(GlanceImageServiceTest, self).setUp()
3744- self.stubs = stubout.StubOutForTesting()
3745- fakes.stub_out_glance(self.stubs)
3746- fakes.stub_out_compute_api_snapshot(self.stubs)
3747- service_class = 'nova.image.glance.GlanceImageService'
3748- self.service = utils.import_object(service_class)
3749- self.context = context.RequestContext('fake', 'fake')
3750- self.service.delete_all()
3751- self.sent_to_glance = {}
3752- fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance)
3753-
3754- def tearDown(self):
3755- self.stubs.UnsetAll()
3756- super(GlanceImageServiceTest, self).tearDown()
3757-
3758- def test_create_with_instance_id(self):
3759- """Ensure instance_id is persisted as an image-property"""
3760- fixture = {'name': 'test image',
3761- 'is_public': False,
3762- 'properties': {'instance_id': '42', 'user_id': 'fake'}}
3763-
3764- image_id = self.service.create(self.context, fixture)['id']
3765- expected = fixture
3766- self.assertDictMatch(self.sent_to_glance['metadata'], expected)
3767-
3768- image_meta = self.service.show(self.context, image_id)
3769- expected = {'id': image_id,
3770- 'name': 'test image',
3771- 'is_public': False,
3772- 'properties': {'instance_id': '42', 'user_id': 'fake'}}
3773- self.assertDictMatch(image_meta, expected)
3774-
3775- image_metas = self.service.detail(self.context)
3776- self.assertDictMatch(image_metas[0], expected)
3777-
3778- def test_create_without_instance_id(self):
3779- """
3780- Ensure we can create an image without having to specify an
3781- instance_id. Public images are an example of an image not tied to an
3782- instance.
3783- """
3784- fixture = {'name': 'test image'}
3785- image_id = self.service.create(self.context, fixture)['id']
3786-
3787- expected = {'name': 'test image', 'properties': {}}
3788- self.assertDictMatch(self.sent_to_glance['metadata'], expected)
3789-
3790- def test_index_default_limit(self):
3791- fixtures = []
3792- ids = []
3793- for i in range(10):
3794- fixture = self._make_fixture('TestImage %d' % (i))
3795- fixtures.append(fixture)
3796- ids.append(self.service.create(self.context, fixture)['id'])
3797-
3798- image_metas = self.service.index(self.context)
3799- i = 0
3800- for meta in image_metas:
3801- expected = {'id': 'DONTCARE',
3802- 'name': 'TestImage %d' % (i)}
3803- self.assertDictMatch(meta, expected)
3804- i = i + 1
3805-
3806- def test_index_marker(self):
3807- fixtures = []
3808- ids = []
3809- for i in range(10):
3810- fixture = self._make_fixture('TestImage %d' % (i))
3811- fixtures.append(fixture)
3812- ids.append(self.service.create(self.context, fixture)['id'])
3813-
3814- image_metas = self.service.index(self.context, marker=ids[1])
3815- self.assertEquals(len(image_metas), 8)
3816- i = 2
3817- for meta in image_metas:
3818- expected = {'id': 'DONTCARE',
3819- 'name': 'TestImage %d' % (i)}
3820- self.assertDictMatch(meta, expected)
3821- i = i + 1
3822-
3823- def test_index_limit(self):
3824- fixtures = []
3825- ids = []
3826- for i in range(10):
3827- fixture = self._make_fixture('TestImage %d' % (i))
3828- fixtures.append(fixture)
3829- ids.append(self.service.create(self.context, fixture)['id'])
3830-
3831- image_metas = self.service.index(self.context, limit=3)
3832- self.assertEquals(len(image_metas), 3)
3833-
3834- def test_index_marker_and_limit(self):
3835- fixtures = []
3836- ids = []
3837- for i in range(10):
3838- fixture = self._make_fixture('TestImage %d' % (i))
3839- fixtures.append(fixture)
3840- ids.append(self.service.create(self.context, fixture)['id'])
3841-
3842- image_metas = self.service.index(self.context, marker=ids[3], limit=1)
3843- self.assertEquals(len(image_metas), 1)
3844- i = 4
3845- for meta in image_metas:
3846- expected = {'id': 'DONTCARE',
3847- 'name': 'TestImage %d' % (i)}
3848- self.assertDictMatch(meta, expected)
3849- i = i + 1
3850-
3851- def test_detail_marker(self):
3852- fixtures = []
3853- ids = []
3854- for i in range(10):
3855- fixture = self._make_fixture('TestImage %d' % (i))
3856- fixtures.append(fixture)
3857- ids.append(self.service.create(self.context, fixture)['id'])
3858-
3859- image_metas = self.service.detail(self.context, marker=ids[1])
3860- self.assertEquals(len(image_metas), 8)
3861- i = 2
3862- for meta in image_metas:
3863- expected = {
3864- 'id': 'DONTCARE',
3865- 'status': None,
3866- 'is_public': True,
3867- 'name': 'TestImage %d' % (i),
3868- 'properties': {
3869- 'updated': None,
3870- 'created': None,
3871- },
3872- }
3873-
3874- self.assertDictMatch(meta, expected)
3875- i = i + 1
3876-
3877- def test_detail_limit(self):
3878- fixtures = []
3879- ids = []
3880- for i in range(10):
3881- fixture = self._make_fixture('TestImage %d' % (i))
3882- fixtures.append(fixture)
3883- ids.append(self.service.create(self.context, fixture)['id'])
3884-
3885- image_metas = self.service.detail(self.context, limit=3)
3886- self.assertEquals(len(image_metas), 3)
3887-
3888- def test_detail_marker_and_limit(self):
3889- fixtures = []
3890- ids = []
3891- for i in range(10):
3892- fixture = self._make_fixture('TestImage %d' % (i))
3893- fixtures.append(fixture)
3894- ids.append(self.service.create(self.context, fixture)['id'])
3895-
3896- image_metas = self.service.detail(self.context, marker=ids[3], limit=3)
3897- self.assertEquals(len(image_metas), 3)
3898- i = 4
3899- for meta in image_metas:
3900- expected = {
3901- 'id': 'DONTCARE',
3902- 'status': None,
3903- 'is_public': True,
3904- 'name': 'TestImage %d' % (i),
3905- 'properties': {
3906- 'updated': None, 'created': None},
3907- }
3908- self.assertDictMatch(meta, expected)
3909- i = i + 1
3910-
3911-
3912-class ImageControllerWithGlanceServiceTest(test.TestCase):
3913+NS = "{http://docs.openstack.org/compute/api/v1.1}"
3914+ATOMNS = "{http://www.w3.org/2005/Atom}"
3915+NOW_API_FORMAT = "2010-10-11T10:30:22Z"
3916+
3917+
3918+class ImagesTest(test.TestCase):
3919 """
3920 Test of the OpenStack API /images application controller w/Glance.
3921 """
3922- NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
3923- NOW_API_FORMAT = "2010-10-11T10:30:22Z"
3924
3925 def setUp(self):
3926 """Run before each test."""
3927- super(ImageControllerWithGlanceServiceTest, self).setUp()
3928- self.flags(image_service='nova.image.glance.GlanceImageService')
3929+ super(ImagesTest, self).setUp()
3930 self.stubs = stubout.StubOutForTesting()
3931 fakes.stub_out_networking(self.stubs)
3932 fakes.stub_out_rate_limiting(self.stubs)
3933 fakes.stub_out_key_pair_funcs(self.stubs)
3934- self.fixtures = self._make_image_fixtures()
3935- fakes.stub_out_glance(self.stubs, initial_fixtures=self.fixtures)
3936 fakes.stub_out_compute_api_snapshot(self.stubs)
3937 fakes.stub_out_compute_api_backup(self.stubs)
3938+ fakes.stub_out_glance(self.stubs)
3939
3940 def tearDown(self):
3941 """Run after each test."""
3942 self.stubs.UnsetAll()
3943- super(ImageControllerWithGlanceServiceTest, self).tearDown()
3944+ super(ImagesTest, self).tearDown()
3945
3946 def _get_fake_context(self):
3947 class Context(object):
3948 project_id = 'fake'
3949+ auth_token = True
3950 return Context()
3951
3952- def _applicable_fixture(self, fixture, user_id):
3953- """Determine if this fixture is applicable for given user id."""
3954- is_public = fixture["is_public"]
3955- try:
3956- uid = fixture["properties"]["user_id"]
3957- except KeyError:
3958- uid = None
3959- return uid == user_id or is_public
3960-
3961 def test_get_image_index(self):
3962 request = webob.Request.blank('/v1.0/images')
3963- response = request.get_response(fakes.wsgi_app())
3964+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
3965+ response = request.get_response(app)
3966
3967 response_dict = json.loads(response.body)
3968 response_list = response_dict["images"]
3969@@ -365,15 +82,22 @@
3970 {'id': 125, 'name': 'saving snapshot'},
3971 {'id': 126, 'name': 'active snapshot'},
3972 {'id': 127, 'name': 'killed snapshot'},
3973+<<<<<<< TREE
3974 {'id': 128, 'name': 'deleted snapshot'},
3975 {'id': 129, 'name': 'pending_delete snapshot'},
3976 {'id': 131, 'name': None}]
3977+=======
3978+ {'id': 128, 'name': 'deleted snapshot'},
3979+ {'id': 129, 'name': 'pending_delete snapshot'},
3980+ {'id': 130, 'name': None}]
3981+>>>>>>> MERGE-SOURCE
3982
3983 self.assertDictListMatch(response_list, expected)
3984
3985 def test_get_image(self):
3986 request = webob.Request.blank('/v1.0/images/123')
3987- response = request.get_response(fakes.wsgi_app())
3988+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
3989+ response = request.get_response(app)
3990
3991 self.assertEqual(200, response.status_int)
3992
3993@@ -383,18 +107,19 @@
3994 "image": {
3995 "id": 123,
3996 "name": "public image",
3997- "updated": self.NOW_API_FORMAT,
3998- "created": self.NOW_API_FORMAT,
3999+ "updated": NOW_API_FORMAT,
4000+ "created": NOW_API_FORMAT,
4001 "status": "ACTIVE",
4002 "progress": 100,
4003 },
4004 }
4005
4006- self.assertEqual(expected_image, actual_image)
4007+ self.assertDictMatch(expected_image, actual_image)
4008
4009 def test_get_image_v1_1(self):
4010 request = webob.Request.blank('/v1.1/fake/images/124')
4011- response = request.get_response(fakes.wsgi_app())
4012+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
4013+ response = request.get_response(app)
4014
4015 actual_image = json.loads(response.body)
4016
4017@@ -405,14 +130,20 @@
4018
4019 expected_image = {
4020 "image": {
4021- "id": 124,
4022+ "id": "124",
4023 "name": "queued snapshot",
4024+<<<<<<< TREE
4025 "updated": self.NOW_API_FORMAT,
4026 "created": self.NOW_API_FORMAT,
4027 "status": "SAVING",
4028+=======
4029+ "updated": NOW_API_FORMAT,
4030+ "created": NOW_API_FORMAT,
4031+ "status": "SAVING",
4032+>>>>>>> MERGE-SOURCE
4033 "progress": 0,
4034 'server': {
4035- 'id': 42,
4036+ 'id': '42',
4037 "links": [{
4038 "rel": "self",
4039 "href": server_href,
4040@@ -442,11 +173,12 @@
4041 def test_get_image_xml(self):
4042 request = webob.Request.blank('/v1.0/images/123')
4043 request.accept = "application/xml"
4044- response = request.get_response(fakes.wsgi_app())
4045+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
4046+ response = request.get_response(app)
4047
4048 actual_image = minidom.parseString(response.body.replace(" ", ""))
4049
4050- expected_now = self.NOW_API_FORMAT
4051+ expected_now = NOW_API_FORMAT
4052 expected_image = minidom.parseString("""
4053 <image id="123"
4054 name="public image"
4055@@ -460,15 +192,24 @@
4056 self.assertEqual(expected_image.toxml(), actual_image.toxml())
4057
4058 def test_get_image_xml_no_name(self):
4059+<<<<<<< TREE
4060 request = webob.Request.blank('/v1.0/images/131')
4061+=======
4062+ request = webob.Request.blank('/v1.0/images/130')
4063+>>>>>>> MERGE-SOURCE
4064 request.accept = "application/xml"
4065- response = request.get_response(fakes.wsgi_app())
4066+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
4067+ response = request.get_response(app)
4068
4069 actual_image = minidom.parseString(response.body.replace(" ", ""))
4070
4071- expected_now = self.NOW_API_FORMAT
4072+ expected_now = NOW_API_FORMAT
4073 expected_image = minidom.parseString("""
4074+<<<<<<< TREE
4075 <image id="131"
4076+=======
4077+ <image id="130"
4078+>>>>>>> MERGE-SOURCE
4079 name="None"
4080 updated="%(expected_now)s"
4081 created="%(expected_now)s"
4082@@ -503,12 +244,10 @@
4083
4084 expected = minidom.parseString("""
4085 <itemNotFound code="404"
4086- xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
4087- <message>
4088- Image not found.
4089- </message>
4090+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
4091+ <message>Image not found.</message>
4092 </itemNotFound>
4093- """.replace(" ", ""))
4094+ """.replace(" ", "").replace("\n", ""))
4095
4096 actual = minidom.parseString(response.body.replace(" ", ""))
4097
4098@@ -540,12 +279,10 @@
4099 # because the element hasn't changed definition
4100 expected = minidom.parseString("""
4101 <itemNotFound code="404"
4102- xmlns="http://docs.openstack.org/compute/api/v1.1">
4103- <message>
4104- Image not found.
4105- </message>
4106+ xmlns="http://docs.openstack.org/compute/api/v1.1">
4107+ <message>Image not found.</message>
4108 </itemNotFound>
4109- """.replace(" ", ""))
4110+ """.replace(" ", "").replace("\n", ""))
4111
4112 actual = minidom.parseString(response.body.replace(" ", ""))
4113
4114@@ -553,41 +290,133 @@
4115
4116 def test_get_image_index_v1_1(self):
4117 request = webob.Request.blank('/v1.1/fake/images')
4118- response = request.get_response(fakes.wsgi_app())
4119+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
4120+ response = request.get_response(app)
4121
4122 response_dict = json.loads(response.body)
4123 response_list = response_dict["images"]
4124
4125- fixtures = copy.copy(self.fixtures)
4126-
4127- for image in fixtures:
4128- if not self._applicable_fixture(image, "fake"):
4129- fixtures.remove(image)
4130- continue
4131-
4132- href = "http://localhost/v1.1/fake/images/%s" % image["id"]
4133- bookmark = "http://localhost/fake/images/%s" % image["id"]
4134- test_image = {
4135- "id": image["id"],
4136- "name": image["name"],
4137- "links": [
4138- {
4139- "rel": "self",
4140- "href": href,
4141- },
4142- {
4143- "rel": "bookmark",
4144- "href": bookmark,
4145- },
4146- ],
4147- }
4148- self.assertTrue(test_image in response_list)
4149-
4150- self.assertEqual(len(response_list), len(fixtures))
4151+ expected = [
4152+ {
4153+ "id": "123",
4154+ "name": "public image",
4155+ "links": [
4156+ {
4157+ "rel": "self",
4158+ "href": "http://localhost/v1.1/fake/images/123",
4159+ },
4160+ {
4161+ "rel": "bookmark",
4162+ "href": "http://localhost/fake/images/123",
4163+ },
4164+ ],
4165+ },
4166+ {
4167+ "id": "124",
4168+ "name": "queued snapshot",
4169+ "links": [
4170+ {
4171+ "rel": "self",
4172+ "href": "http://localhost/v1.1/fake/images/124",
4173+ },
4174+ {
4175+ "rel": "bookmark",
4176+ "href": "http://localhost/fake/images/124",
4177+ },
4178+ ],
4179+ },
4180+ {
4181+ "id": "125",
4182+ "name": "saving snapshot",
4183+ "links": [
4184+ {
4185+ "rel": "self",
4186+ "href": "http://localhost/v1.1/fake/images/125",
4187+ },
4188+ {
4189+ "rel": "bookmark",
4190+ "href": "http://localhost/fake/images/125",
4191+ },
4192+ ],
4193+ },
4194+ {
4195+ "id": "126",
4196+ "name": "active snapshot",
4197+ "links": [
4198+ {
4199+ "rel": "self",
4200+ "href": "http://localhost/v1.1/fake/images/126",
4201+ },
4202+ {
4203+ "rel": "bookmark",
4204+ "href": "http://localhost/fake/images/126",
4205+ },
4206+ ],
4207+ },
4208+ {
4209+ "id": "127",
4210+ "name": "killed snapshot",
4211+ "links": [
4212+ {
4213+ "rel": "self",
4214+ "href": "http://localhost/v1.1/fake/images/127",
4215+ },
4216+ {
4217+ "rel": "bookmark",
4218+ "href": "http://localhost/fake/images/127",
4219+ },
4220+ ],
4221+ },
4222+ {
4223+ "id": "128",
4224+ "name": "deleted snapshot",
4225+ "links": [
4226+ {
4227+ "rel": "self",
4228+ "href": "http://localhost/v1.1/fake/images/128",
4229+ },
4230+ {
4231+ "rel": "bookmark",
4232+ "href": "http://localhost/fake/images/128",
4233+ },
4234+ ],
4235+ },
4236+ {
4237+ "id": "129",
4238+ "name": "pending_delete snapshot",
4239+ "links": [
4240+ {
4241+ "rel": "self",
4242+ "href": "http://localhost/v1.1/fake/images/129",
4243+ },
4244+ {
4245+ "rel": "bookmark",
4246+ "href": "http://localhost/fake/images/129",
4247+ },
4248+ ],
4249+ },
4250+ {
4251+ "id": "130",
4252+ "name": None,
4253+ "links": [
4254+ {
4255+ "rel": "self",
4256+ "href": "http://localhost/v1.1/fake/images/130",
4257+ },
4258+ {
4259+ "rel": "bookmark",
4260+ "href": "http://localhost/fake/images/130",
4261+ },
4262+ ],
4263+ },
4264+ ]
4265+
4266+ self.assertDictListMatch(response_list, expected)
4267
4268 def test_get_image_details(self):
4269 request = webob.Request.blank('/v1.0/images/detail')
4270- response = request.get_response(fakes.wsgi_app())
4271+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
4272+ response = request.get_response(app)
4273
4274 response_dict = json.loads(response.body)
4275 response_list = response_dict["images"]
4276@@ -595,53 +424,74 @@
4277 expected = [{
4278 'id': 123,
4279 'name': 'public image',
4280- 'updated': self.NOW_API_FORMAT,
4281- 'created': self.NOW_API_FORMAT,
4282+ 'updated': NOW_API_FORMAT,
4283+ 'created': NOW_API_FORMAT,
4284 'status': 'ACTIVE',
4285 'progress': 100,
4286 },
4287 {
4288 'id': 124,
4289 'name': 'queued snapshot',
4290+<<<<<<< TREE
4291 'updated': self.NOW_API_FORMAT,
4292 'created': self.NOW_API_FORMAT,
4293 'status': 'SAVING',
4294+=======
4295+ 'updated': NOW_API_FORMAT,
4296+ 'created': NOW_API_FORMAT,
4297+ 'status': 'SAVING',
4298+>>>>>>> MERGE-SOURCE
4299 'progress': 0,
4300 },
4301 {
4302 'id': 125,
4303 'name': 'saving snapshot',
4304- 'updated': self.NOW_API_FORMAT,
4305- 'created': self.NOW_API_FORMAT,
4306+ 'updated': NOW_API_FORMAT,
4307+ 'created': NOW_API_FORMAT,
4308 'status': 'SAVING',
4309 'progress': 0,
4310 },
4311 {
4312 'id': 126,
4313 'name': 'active snapshot',
4314- 'updated': self.NOW_API_FORMAT,
4315- 'created': self.NOW_API_FORMAT,
4316+ 'updated': NOW_API_FORMAT,
4317+ 'created': NOW_API_FORMAT,
4318 'status': 'ACTIVE',
4319 'progress': 100,
4320 },
4321 {
4322 'id': 127,
4323 'name': 'killed snapshot',
4324- 'updated': self.NOW_API_FORMAT,
4325- 'created': self.NOW_API_FORMAT,
4326- 'status': 'ERROR',
4327- 'progress': 0,
4328- },
4329- {
4330- 'id': 128,
4331- 'name': 'deleted snapshot',
4332- 'updated': self.NOW_API_FORMAT,
4333- 'created': self.NOW_API_FORMAT,
4334- 'status': 'DELETED',
4335+<<<<<<< TREE
4336+ 'updated': self.NOW_API_FORMAT,
4337+ 'created': self.NOW_API_FORMAT,
4338+ 'status': 'ERROR',
4339+ 'progress': 0,
4340+ },
4341+ {
4342+ 'id': 128,
4343+ 'name': 'deleted snapshot',
4344+ 'updated': self.NOW_API_FORMAT,
4345+ 'created': self.NOW_API_FORMAT,
4346+ 'status': 'DELETED',
4347+=======
4348+ 'updated': NOW_API_FORMAT,
4349+ 'created': NOW_API_FORMAT,
4350+ 'status': 'ERROR',
4351+ 'progress': 0,
4352+ },
4353+ {
4354+ 'id': 128,
4355+ 'name': 'deleted snapshot',
4356+ 'updated': NOW_API_FORMAT,
4357+ 'created': NOW_API_FORMAT,
4358+ 'status': 'DELETED',
4359+>>>>>>> MERGE-SOURCE
4360 'progress': 0,
4361 },
4362 {
4363 'id': 129,
4364+<<<<<<< TREE
4365 'name': 'pending_delete snapshot',
4366 'updated': self.NOW_API_FORMAT,
4367 'created': self.NOW_API_FORMAT,
4368@@ -650,9 +500,19 @@
4369 },
4370 {
4371 'id': 131,
4372+=======
4373+ 'name': 'pending_delete snapshot',
4374+ 'updated': NOW_API_FORMAT,
4375+ 'created': NOW_API_FORMAT,
4376+ 'status': 'DELETED',
4377+ 'progress': 0,
4378+ },
4379+ {
4380+ 'id': 130,
4381+>>>>>>> MERGE-SOURCE
4382 'name': None,
4383- 'updated': self.NOW_API_FORMAT,
4384- 'created': self.NOW_API_FORMAT,
4385+ 'updated': NOW_API_FORMAT,
4386+ 'created': NOW_API_FORMAT,
4387 'status': 'ACTIVE',
4388 'progress': 100,
4389 }]
4390@@ -661,7 +521,8 @@
4391
4392 def test_get_image_details_v1_1(self):
4393 request = webob.Request.blank('/v1.1/fake/images/detail')
4394- response = request.get_response(fakes.wsgi_app())
4395+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
4396+ response = request.get_response(app)
4397
4398 response_dict = json.loads(response.body)
4399 response_list = response_dict["images"]
4400@@ -669,11 +530,11 @@
4401 server_bookmark = "http://localhost/servers/42"
4402
4403 expected = [{
4404- 'id': 123,
4405+ 'id': '123',
4406 'name': 'public image',
4407- 'metadata': {},
4408- 'updated': self.NOW_API_FORMAT,
4409- 'created': self.NOW_API_FORMAT,
4410+ 'metadata': {'key1': 'value1'},
4411+ 'updated': NOW_API_FORMAT,
4412+ 'created': NOW_API_FORMAT,
4413 'status': 'ACTIVE',
4414 'progress': 100,
4415 "links": [{
4416@@ -686,18 +547,24 @@
4417 }],
4418 },
4419 {
4420- 'id': 124,
4421+ 'id': '124',
4422 'name': 'queued snapshot',
4423 'metadata': {
4424 u'instance_ref': u'http://localhost/v1.1/servers/42',
4425 u'user_id': u'fake',
4426 },
4427+<<<<<<< TREE
4428 'updated': self.NOW_API_FORMAT,
4429 'created': self.NOW_API_FORMAT,
4430 'status': 'SAVING',
4431+=======
4432+ 'updated': NOW_API_FORMAT,
4433+ 'created': NOW_API_FORMAT,
4434+ 'status': 'SAVING',
4435+>>>>>>> MERGE-SOURCE
4436 'progress': 0,
4437 'server': {
4438- 'id': 42,
4439+ 'id': '42',
4440 "links": [{
4441 "rel": "self",
4442 "href": server_href,
4443@@ -717,18 +584,18 @@
4444 }],
4445 },
4446 {
4447- 'id': 125,
4448+ 'id': '125',
4449 'name': 'saving snapshot',
4450 'metadata': {
4451 u'instance_ref': u'http://localhost/v1.1/servers/42',
4452 u'user_id': u'fake',
4453 },
4454- 'updated': self.NOW_API_FORMAT,
4455- 'created': self.NOW_API_FORMAT,
4456+ 'updated': NOW_API_FORMAT,
4457+ 'created': NOW_API_FORMAT,
4458 'status': 'SAVING',
4459 'progress': 0,
4460 'server': {
4461- 'id': 42,
4462+ 'id': '42',
4463 "links": [{
4464 "rel": "self",
4465 "href": server_href,
4466@@ -748,18 +615,18 @@
4467 }],
4468 },
4469 {
4470- 'id': 126,
4471+ 'id': '126',
4472 'name': 'active snapshot',
4473 'metadata': {
4474 u'instance_ref': u'http://localhost/v1.1/servers/42',
4475 u'user_id': u'fake',
4476 },
4477- 'updated': self.NOW_API_FORMAT,
4478- 'created': self.NOW_API_FORMAT,
4479+ 'updated': NOW_API_FORMAT,
4480+ 'created': NOW_API_FORMAT,
4481 'status': 'ACTIVE',
4482 'progress': 100,
4483 'server': {
4484- 'id': 42,
4485+ 'id': '42',
4486 "links": [{
4487 "rel": "self",
4488 "href": server_href,
4489@@ -779,18 +646,24 @@
4490 }],
4491 },
4492 {
4493- 'id': 127,
4494+ 'id': '127',
4495 'name': 'killed snapshot',
4496 'metadata': {
4497 u'instance_ref': u'http://localhost/v1.1/servers/42',
4498 u'user_id': u'fake',
4499 },
4500+<<<<<<< TREE
4501 'updated': self.NOW_API_FORMAT,
4502 'created': self.NOW_API_FORMAT,
4503 'status': 'ERROR',
4504+=======
4505+ 'updated': NOW_API_FORMAT,
4506+ 'created': NOW_API_FORMAT,
4507+ 'status': 'ERROR',
4508+>>>>>>> MERGE-SOURCE
4509 'progress': 0,
4510 'server': {
4511- 'id': 42,
4512+ 'id': '42',
4513 "links": [{
4514 "rel": "self",
4515 "href": server_href,
4516@@ -810,6 +683,7 @@
4517 }],
4518 },
4519 {
4520+<<<<<<< TREE
4521 'id': 128,
4522 'name': 'deleted snapshot',
4523 'metadata': {
4524@@ -862,6 +736,60 @@
4525 "href": server_bookmark,
4526 }],
4527 },
4528+=======
4529+ 'id': '128',
4530+ 'name': 'deleted snapshot',
4531+ 'metadata': {
4532+ u'instance_ref': u'http://localhost/v1.1/servers/42',
4533+ u'user_id': u'fake',
4534+ },
4535+ 'updated': NOW_API_FORMAT,
4536+ 'created': NOW_API_FORMAT,
4537+ 'status': 'DELETED',
4538+ 'progress': 0,
4539+ 'server': {
4540+ 'id': '42',
4541+ "links": [{
4542+ "rel": "self",
4543+ "href": server_href,
4544+ },
4545+ {
4546+ "rel": "bookmark",
4547+ "href": server_bookmark,
4548+ }],
4549+ },
4550+ "links": [{
4551+ "rel": "self",
4552+ "href": "http://localhost/v1.1/fake/images/128",
4553+ },
4554+ {
4555+ "rel": "bookmark",
4556+ "href": "http://localhost/fake/images/128",
4557+ }],
4558+ },
4559+ {
4560+ 'id': '129',
4561+ 'name': 'pending_delete snapshot',
4562+ 'metadata': {
4563+ u'instance_ref': u'http://localhost/v1.1/servers/42',
4564+ u'user_id': u'fake',
4565+ },
4566+ 'updated': NOW_API_FORMAT,
4567+ 'created': NOW_API_FORMAT,
4568+ 'status': 'DELETED',
4569+ 'progress': 0,
4570+ 'server': {
4571+ 'id': '42',
4572+ "links": [{
4573+ "rel": "self",
4574+ "href": server_href,
4575+ },
4576+ {
4577+ "rel": "bookmark",
4578+ "href": server_bookmark,
4579+ }],
4580+ },
4581+>>>>>>> MERGE-SOURCE
4582 "links": [{
4583 "rel": "self",
4584 "href": "http://localhost/v1.1/fake/images/129",
4585@@ -871,6 +799,7 @@
4586 "href": "http://localhost/fake/images/129",
4587 }],
4588 },
4589+<<<<<<< TREE
4590 {
4591 'id': 131,
4592 'name': None,
4593@@ -888,6 +817,25 @@
4594 "href": "http://localhost/fake/images/131",
4595 }],
4596 },
4597+=======
4598+ {
4599+ 'id': '130',
4600+ 'name': None,
4601+ 'metadata': {},
4602+ 'updated': NOW_API_FORMAT,
4603+ 'created': NOW_API_FORMAT,
4604+ 'status': 'ACTIVE',
4605+ 'progress': 100,
4606+ "links": [{
4607+ "rel": "self",
4608+ "href": "http://localhost/v1.1/fake/images/130",
4609+ },
4610+ {
4611+ "rel": "bookmark",
4612+ "href": "http://localhost/fake/images/130",
4613+ }],
4614+ },
4615+>>>>>>> MERGE-SOURCE
4616 ]
4617
4618 self.assertDictListMatch(expected, response_list)
4619@@ -1097,11 +1045,12 @@
4620
4621 def test_get_image_found(self):
4622 req = webob.Request.blank('/v1.0/images/123')
4623- res = req.get_response(fakes.wsgi_app())
4624+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
4625+ res = req.get_response(app)
4626 image_meta = json.loads(res.body)['image']
4627 expected = {'id': 123, 'name': 'public image',
4628- 'updated': self.NOW_API_FORMAT,
4629- 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE',
4630+ 'updated': NOW_API_FORMAT,
4631+ 'created': NOW_API_FORMAT, 'status': 'ACTIVE',
4632 'progress': 100}
4633 self.assertDictMatch(image_meta, expected)
4634
4635@@ -1110,6 +1059,7 @@
4636 res = req.get_response(fakes.wsgi_app())
4637 self.assertEqual(res.status_int, 404)
4638
4639+<<<<<<< TREE
4640 def test_get_image_not_owned(self):
4641 """We should return a 404 if we request an image that doesn't belong
4642 to us
4643@@ -1118,6 +1068,8 @@
4644 res = req.get_response(fakes.wsgi_app())
4645 self.assertEqual(res.status_int, 404)
4646
4647+=======
4648+>>>>>>> MERGE-SOURCE
4649 def test_create_image(self):
4650 body = dict(image=dict(serverId='123', name='Snapshot 1'))
4651 req = webob.Request.blank('/v1.0/images')
4652@@ -1160,6 +1112,7 @@
4653 response = req.get_response(fakes.wsgi_app())
4654 self.assertEqual(400, response.status_int)
4655
4656+<<<<<<< TREE
4657 @classmethod
4658 def _make_image_fixtures(cls):
4659 image_id = 123
4660@@ -1205,6 +1158,8 @@
4661
4662 return fixtures
4663
4664+=======
4665+>>>>>>> MERGE-SOURCE
4666
4667 class ImageXMLSerializationTest(test.TestCase):
4668
4669@@ -1214,6 +1169,51 @@
4670 IMAGE_HREF = 'http://localhost/v1.1/fake/images/%s'
4671 IMAGE_BOOKMARK = 'http://localhost/fake/images/%s'
4672
4673+ def test_xml_declaration(self):
4674+ serializer = images.ImageXMLSerializer()
4675+
4676+ fixture = {
4677+ 'image': {
4678+ 'id': 1,
4679+ 'name': 'Image1',
4680+ 'created': self.TIMESTAMP,
4681+ 'updated': self.TIMESTAMP,
4682+ 'status': 'ACTIVE',
4683+ 'progress': 80,
4684+ 'server': {
4685+ 'id': '1',
4686+ 'links': [
4687+ {
4688+ 'href': self.SERVER_HREF,
4689+ 'rel': 'self',
4690+ },
4691+ {
4692+ 'href': self.SERVER_BOOKMARK,
4693+ 'rel': 'bookmark',
4694+ },
4695+ ],
4696+ },
4697+ 'metadata': {
4698+ 'key1': 'value1',
4699+ },
4700+ 'links': [
4701+ {
4702+ 'href': self.IMAGE_HREF % 1,
4703+ 'rel': 'self',
4704+ },
4705+ {
4706+ 'href': self.IMAGE_BOOKMARK % 1,
4707+ 'rel': 'bookmark',
4708+ },
4709+ ],
4710+ },
4711+ }
4712+
4713+ output = serializer.serialize(fixture, 'show')
4714+ print output
4715+ has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>")
4716+ self.assertTrue(has_dec)
4717+
4718 def test_show(self):
4719 serializer = images.ImageXMLSerializer()
4720
4721@@ -1226,7 +1226,7 @@
4722 'status': 'ACTIVE',
4723 'progress': 80,
4724 'server': {
4725- 'id': 1,
4726+ 'id': '1',
4727 'links': [
4728 {
4729 'href': self.SERVER_HREF,
4730@@ -1255,37 +1255,35 @@
4731 }
4732
4733 output = serializer.serialize(fixture, 'show')
4734- actual = minidom.parseString(output.replace(" ", ""))
4735-
4736- expected_server_href = self.SERVER_HREF
4737- expected_server_bookmark = self.SERVER_BOOKMARK
4738- expected_href = self.IMAGE_HREF % 1
4739- expected_bookmark = self.IMAGE_BOOKMARK % 1
4740- expected_now = self.TIMESTAMP
4741- expected = minidom.parseString("""
4742- <image id="1"
4743- xmlns="http://docs.openstack.org/compute/api/v1.1"
4744- xmlns:atom="http://www.w3.org/2005/Atom"
4745- name="Image1"
4746- updated="%(expected_now)s"
4747- created="%(expected_now)s"
4748- status="ACTIVE"
4749- progress="80">
4750- <server id="1">
4751- <atom:link rel="self" href="%(expected_server_href)s"/>
4752- <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
4753- </server>
4754- <metadata>
4755- <meta key="key1">
4756- value1
4757- </meta>
4758- </metadata>
4759- <atom:link href="%(expected_href)s" rel="self"/>
4760- <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
4761- </image>
4762- """.replace(" ", "") % (locals()))
4763-
4764- self.assertEqual(expected.toxml(), actual.toxml())
4765+ print output
4766+ root = etree.XML(output)
4767+ xmlutil.validate_schema(root, 'image')
4768+ image_dict = fixture['image']
4769+
4770+ for key in ['name', 'id', 'updated', 'created', 'status', 'progress']:
4771+ self.assertEqual(root.get(key), str(image_dict[key]))
4772+
4773+ link_nodes = root.findall('{0}link'.format(ATOMNS))
4774+ self.assertEqual(len(link_nodes), 2)
4775+ for i, link in enumerate(image_dict['links']):
4776+ for key, value in link.items():
4777+ self.assertEqual(link_nodes[i].get(key), value)
4778+
4779+ metadata_root = root.find('{0}metadata'.format(NS))
4780+ metadata_elems = metadata_root.findall('{0}meta'.format(NS))
4781+ self.assertEqual(len(metadata_elems), 1)
4782+ for i, metadata_elem in enumerate(metadata_elems):
4783+ (meta_key, meta_value) = image_dict['metadata'].items()[i]
4784+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
4785+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
4786+
4787+ server_root = root.find('{0}server'.format(NS))
4788+ self.assertEqual(server_root.get('id'), image_dict['server']['id'])
4789+ link_nodes = server_root.findall('{0}link'.format(ATOMNS))
4790+ self.assertEqual(len(link_nodes), 2)
4791+ for i, link in enumerate(image_dict['server']['links']):
4792+ for key, value in link.items():
4793+ self.assertEqual(link_nodes[i].get(key), value)
4794
4795 def test_show_zero_metadata(self):
4796 serializer = images.ImageXMLSerializer()
4797@@ -1298,7 +1296,7 @@
4798 'updated': self.TIMESTAMP,
4799 'status': 'ACTIVE',
4800 'server': {
4801- 'id': 1,
4802+ 'id': '1',
4803 'links': [
4804 {
4805 'href': self.SERVER_HREF,
4806@@ -1325,31 +1323,31 @@
4807 }
4808
4809 output = serializer.serialize(fixture, 'show')
4810- actual = minidom.parseString(output.replace(" ", ""))
4811-
4812- expected_server_href = self.SERVER_HREF
4813- expected_server_bookmark = self.SERVER_BOOKMARK
4814- expected_href = self.IMAGE_HREF % 1
4815- expected_bookmark = self.IMAGE_BOOKMARK % 1
4816- expected_now = self.TIMESTAMP
4817- expected = minidom.parseString("""
4818- <image id="1"
4819- xmlns="http://docs.openstack.org/compute/api/v1.1"
4820- xmlns:atom="http://www.w3.org/2005/Atom"
4821- name="Image1"
4822- updated="%(expected_now)s"
4823- created="%(expected_now)s"
4824- status="ACTIVE">
4825- <server id="1">
4826- <atom:link rel="self" href="%(expected_server_href)s"/>
4827- <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
4828- </server>
4829- <atom:link href="%(expected_href)s" rel="self"/>
4830- <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
4831- </image>
4832- """.replace(" ", "") % (locals()))
4833-
4834- self.assertEqual(expected.toxml(), actual.toxml())
4835+ print output
4836+ root = etree.XML(output)
4837+ xmlutil.validate_schema(root, 'image')
4838+ image_dict = fixture['image']
4839+
4840+ for key in ['name', 'id', 'updated', 'created', 'status']:
4841+ self.assertEqual(root.get(key), str(image_dict[key]))
4842+
4843+ link_nodes = root.findall('{0}link'.format(ATOMNS))
4844+ self.assertEqual(len(link_nodes), 2)
4845+ for i, link in enumerate(image_dict['links']):
4846+ for key, value in link.items():
4847+ self.assertEqual(link_nodes[i].get(key), value)
4848+
4849+ metadata_root = root.find('{0}metadata'.format(NS))
4850+ meta_nodes = root.findall('{0}meta'.format(ATOMNS))
4851+ self.assertEqual(len(meta_nodes), 0)
4852+
4853+ server_root = root.find('{0}server'.format(NS))
4854+ self.assertEqual(server_root.get('id'), image_dict['server']['id'])
4855+ link_nodes = server_root.findall('{0}link'.format(ATOMNS))
4856+ self.assertEqual(len(link_nodes), 2)
4857+ for i, link in enumerate(image_dict['server']['links']):
4858+ for key, value in link.items():
4859+ self.assertEqual(link_nodes[i].get(key), value)
4860
4861 def test_show_image_no_metadata_key(self):
4862 serializer = images.ImageXMLSerializer()
4863@@ -1362,7 +1360,7 @@
4864 'updated': self.TIMESTAMP,
4865 'status': 'ACTIVE',
4866 'server': {
4867- 'id': 1,
4868+ 'id': '1',
4869 'links': [
4870 {
4871 'href': self.SERVER_HREF,
4872@@ -1388,31 +1386,31 @@
4873 }
4874
4875 output = serializer.serialize(fixture, 'show')
4876- actual = minidom.parseString(output.replace(" ", ""))
4877-
4878- expected_server_href = self.SERVER_HREF
4879- expected_server_bookmark = self.SERVER_BOOKMARK
4880- expected_href = self.IMAGE_HREF % 1
4881- expected_bookmark = self.IMAGE_BOOKMARK % 1
4882- expected_now = self.TIMESTAMP
4883- expected = minidom.parseString("""
4884- <image id="1"
4885- xmlns="http://docs.openstack.org/compute/api/v1.1"
4886- xmlns:atom="http://www.w3.org/2005/Atom"
4887- name="Image1"
4888- updated="%(expected_now)s"
4889- created="%(expected_now)s"
4890- status="ACTIVE">
4891- <server id="1">
4892- <atom:link rel="self" href="%(expected_server_href)s"/>
4893- <atom:link rel="bookmark" href="%(expected_server_bookmark)s"/>
4894- </server>
4895- <atom:link href="%(expected_href)s" rel="self"/>
4896- <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
4897- </image>
4898- """.replace(" ", "") % (locals()))
4899-
4900- self.assertEqual(expected.toxml(), actual.toxml())
4901+ print output
4902+ root = etree.XML(output)
4903+ xmlutil.validate_schema(root, 'image')
4904+ image_dict = fixture['image']
4905+
4906+ for key in ['name', 'id', 'updated', 'created', 'status']:
4907+ self.assertEqual(root.get(key), str(image_dict[key]))
4908+
4909+ link_nodes = root.findall('{0}link'.format(ATOMNS))
4910+ self.assertEqual(len(link_nodes), 2)
4911+ for i, link in enumerate(image_dict['links']):
4912+ for key, value in link.items():
4913+ self.assertEqual(link_nodes[i].get(key), value)
4914+
4915+ metadata_root = root.find('{0}metadata'.format(NS))
4916+ meta_nodes = root.findall('{0}meta'.format(ATOMNS))
4917+ self.assertEqual(len(meta_nodes), 0)
4918+
4919+ server_root = root.find('{0}server'.format(NS))
4920+ self.assertEqual(server_root.get('id'), image_dict['server']['id'])
4921+ link_nodes = server_root.findall('{0}link'.format(ATOMNS))
4922+ self.assertEqual(len(link_nodes), 2)
4923+ for i, link in enumerate(image_dict['server']['links']):
4924+ for key, value in link.items():
4925+ self.assertEqual(link_nodes[i].get(key), value)
4926
4927 def test_show_no_server(self):
4928 serializer = images.ImageXMLSerializer()
4929@@ -1441,30 +1439,30 @@
4930 }
4931
4932 output = serializer.serialize(fixture, 'show')
4933- actual = minidom.parseString(output.replace(" ", ""))
4934-
4935- expected_href = self.IMAGE_HREF % 1
4936- expected_bookmark = self.IMAGE_BOOKMARK % 1
4937- expected_now = self.TIMESTAMP
4938- expected = minidom.parseString("""
4939- <image id="1"
4940- xmlns="http://docs.openstack.org/compute/api/v1.1"
4941- xmlns:atom="http://www.w3.org/2005/Atom"
4942- name="Image1"
4943- updated="%(expected_now)s"
4944- created="%(expected_now)s"
4945- status="ACTIVE">
4946- <metadata>
4947- <meta key="key1">
4948- value1
4949- </meta>
4950- </metadata>
4951- <atom:link href="%(expected_href)s" rel="self"/>
4952- <atom:link href="%(expected_bookmark)s" rel="bookmark"/>
4953- </image>
4954- """.replace(" ", "") % (locals()))
4955-
4956- self.assertEqual(expected.toxml(), actual.toxml())
4957+ print output
4958+ root = etree.XML(output)
4959+ xmlutil.validate_schema(root, 'image')
4960+ image_dict = fixture['image']
4961+
4962+ for key in ['name', 'id', 'updated', 'created', 'status']:
4963+ self.assertEqual(root.get(key), str(image_dict[key]))
4964+
4965+ link_nodes = root.findall('{0}link'.format(ATOMNS))
4966+ self.assertEqual(len(link_nodes), 2)
4967+ for i, link in enumerate(image_dict['links']):
4968+ for key, value in link.items():
4969+ self.assertEqual(link_nodes[i].get(key), value)
4970+
4971+ metadata_root = root.find('{0}metadata'.format(NS))
4972+ metadata_elems = metadata_root.findall('{0}meta'.format(NS))
4973+ self.assertEqual(len(metadata_elems), 1)
4974+ for i, metadata_elem in enumerate(metadata_elems):
4975+ (meta_key, meta_value) = image_dict['metadata'].items()[i]
4976+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
4977+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
4978+
4979+ server_root = root.find('{0}server'.format(NS))
4980+ self.assertEqual(server_root, None)
4981
4982 def test_index(self):
4983 serializer = images.ImageXMLSerializer()
4984@@ -1479,6 +1477,10 @@
4985 'href': self.IMAGE_HREF % 1,
4986 'rel': 'self',
4987 },
4988+ {
4989+ 'href': self.IMAGE_BOOKMARK % 1,
4990+ 'rel': 'bookmark',
4991+ },
4992 ],
4993 },
4994 {
4995@@ -1489,35 +1491,32 @@
4996 'href': self.IMAGE_HREF % 2,
4997 'rel': 'self',
4998 },
4999+ {
5000+ 'href': self.IMAGE_BOOKMARK % 2,
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches