Merge lp:~ethuleau/nova/lp838154 into lp:~hudson-openstack/nova/milestone-proposed
- lp838154
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenStack release team | Pending | ||
Review via email: mp+75845@code.launchpad.net |
Commit message
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 : | # |
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.
Change description sounds unrelated