Merge lp:~rconradharris/glance/allow_upload_on_put into lp:~hudson-openstack/glance/trunk

Proposed by Rick Harris
Status: Merged
Approved by: Jay Pipes
Approved revision: 39
Merged at revision: 35
Proposed branch: lp:~rconradharris/glance/allow_upload_on_put
Merge into: lp:~hudson-openstack/glance/trunk
Diff against target: 870 lines (+242/-177)
16 files modified
doc/source/client.rst (+4/-0)
doc/source/glanceapi.rst (+16/-0)
glance/client.py (+20/-17)
glance/common/utils.py (+1/-2)
glance/registry/client.py (+6/-2)
glance/registry/db/sqlalchemy/api.py (+17/-15)
glance/registry/db/sqlalchemy/models.py (+11/-11)
glance/registry/server.py (+1/-1)
glance/server.py (+110/-70)
glance/store/__init__.py (+4/-6)
glance/store/backends/__init__.py (+4/-6)
glance/store/filesystem.py (+0/-1)
tests/stubs.py (+4/-4)
tests/unit/test_api.py (+8/-4)
tests/unit/test_clients.py (+34/-36)
tests/unit/test_registry_api.py (+2/-2)
To merge this branch: bzr merge lp:~rconradharris/glance/allow_upload_on_put
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Review via email: mp+45492@code.launchpad.net

Description of the change

This merge is in conjunction with lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot

The patch does the following:

  * Image Create (POST) is broken up into 3 steps (reserve, upload and activate); reserve is used to allow the OpenStack API to return metadata about an image where the data has not been uploaded yet
  * Image Update (PUT) now takes image data as the request body and metadata as headers (just like POST); state is enforced so that image data can only be uploaded once.
  * Image statuses were changed to match the OpenStack API (queued, saving, active, killed); NOTE: preparing is not used
  * update_image and add_image client calls now return metadata instead of just the id

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

Hi Rick!

Great work! Here's a couple small things to fix, though :)

1)

8 - raise exception.Duplicate
9 + raise exception.Duplicate(res.read())

11 - raise exception.BadInputError
12 + raise exception.BadInputError(res.read())

The above will read an entire image into the exception when the exception is raised during add_image()! :) Prolly best to remove that...

2)

You did a great job updating the docstrings in glance.server. Please do also update the docs in docs/source/ to match :)

Thanks!
jay

review: Needs Fixing
Revision history for this message
Rick Harris (rconradharris) wrote :

> The above will read an entire image into the exception when the exception is raised during
> add_image()

This is reading the response body (not the request body). In the case of an exception, a non-200 OK, the response body presumably--and in the cases I tested-- contain a helpful error message.

I can't think, off hand, of a case when we'd have an exception AND return the full-image data.

> Please do also update the docs in docs/source/ to match

Good call, will do.

38. By Rick Harris

Merging trunk

39. By Rick Harris

Updating docs

Revision history for this message
Rick Harris (rconradharris) wrote :

Made some small tweaks to the docs.

Revision history for this message
Jay Pipes (jaypipes) wrote :

w00t.

FYI, the only reason I raised the flag about returning res.read() is because I originally had res.read() in there and during review it was requested that I remove it. ;)

-jay

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'doc/source/client.rst'
2--- doc/source/client.rst 2010-12-23 17:31:47 +0000
3+++ doc/source/client.rst 2011-01-11 17:43:19 +0000
4@@ -133,6 +133,10 @@
5 The `image_meta` argument is a mapping containing various image metadata. The
6 `image_data` argument is the disk image data.
7
8+If the data is not yet available, but you would like to reserve a slot in
9+Glance to hold the image, you can create a 'queued' by omitting the
10+`image_data` parameter.
11+
12 The list of metadata that `image_meta` can contain are listed below.
13
14 * `name`
15
16=== modified file 'doc/source/glanceapi.rst'
17--- doc/source/glanceapi.rst 2010-12-23 17:31:47 +0000
18+++ doc/source/glanceapi.rst 2011-01-11 17:43:19 +0000
19@@ -292,3 +292,19 @@
20 be attached to the image. However, keep in mind that the 8K limit on the
21 size of all HTTP headers sent in a request will effectively limit the number
22 of image properties.
23+
24+
25+Updating an Image
26+*****************
27+
28+Glance will view as image metadata any HTTP header that it receives in a
29+`PUT` request where the header key is prefixed with the strings
30+`x-image-meta-` and `x-image-meta-property-`.
31+
32+If an image was previously reserved, and thus is in the `queued` state, then
33+image data can be added by including it as the request body. If the image
34+already as data associated with it (e.g. not in the `queued` state), then
35+including a request body will result in a `409 Conflict` exception.
36+
37+On success, the `PUT` request will return the image metadata encoded as `HTTP`
38+headers.
39
40=== modified file 'glance/client.py'
41--- glance/client.py 2011-01-04 20:32:07 +0000
42+++ glance/client.py 2011-01-11 17:43:19 +0000
43@@ -120,11 +120,11 @@
44 elif status_code == httplib.NOT_FOUND:
45 raise exception.NotFound
46 elif status_code == httplib.CONFLICT:
47- raise exception.Duplicate
48+ raise exception.Duplicate(res.read())
49 elif status_code == httplib.BAD_REQUEST:
50- raise exception.BadInputError
51+ raise exception.BadInputError(res.read())
52 else:
53- raise Exception("Unknown error occurred! %d" % status_code)
54+ raise Exception("Unknown error occurred! %s" % res.__dict__)
55
56 except (socket.error, IOError), e:
57 raise ClientConnectionError("Unable to connect to "
58@@ -203,12 +203,12 @@
59 image = util.get_image_meta_from_headers(res)
60 return image
61
62- def add_image(self, image_meta, image_data=None):
63+ def add_image(self, image_meta=None, image_data=None):
64 """
65 Tells Glance about an image's metadata as well
66 as optionally the image_data itself
67
68- :param image_meta: Mapping of information about the
69+ :param image_meta: Optional Mapping of information about the
70 image
71 :param image_data: Optional string of raw image data
72 or file-like object that can be
73@@ -216,11 +216,9 @@
74
75 :retval The newly-stored image's metadata.
76 """
77- if not image_data and 'location' not in image_meta.keys():
78- raise exception.Invalid("You must either specify a location "
79- "for the image or supply the actual "
80- "image data when adding an image to "
81- "Glance")
82+ if image_meta is None:
83+ image_meta = {}
84+
85 if image_data:
86 if hasattr(image_data, 'read'):
87 # TODO(jaypipes): This is far from efficient. Implement
88@@ -242,17 +240,22 @@
89
90 res = self.do_request("POST", "/images", body, headers)
91 data = json.loads(res.read())
92- return data['image']['id']
93+ return data['image']
94
95- def update_image(self, image_id, image_metadata):
96+ def update_image(self, image_id, image_meta=None, image_data=None):
97 """
98 Updates Glance's information about an image
99 """
100- if 'image' not in image_metadata.keys():
101- image_metadata = dict(image=image_metadata)
102- body = json.dumps(image_metadata)
103- self.do_request("PUT", "/images/%s" % image_id, body)
104- return True
105+ if image_meta:
106+ if 'image' not in image_meta:
107+ image_meta = dict(image=image_meta)
108+
109+ headers = util.image_meta_to_http_headers(image_meta or {})
110+
111+ body = image_data
112+ res = self.do_request("PUT", "/images/%s" % image_id, body, headers)
113+ data = json.loads(res.read())
114+ return data['image']
115
116 def delete_image(self, image_id):
117 """
118
119=== modified file 'glance/common/utils.py'
120--- glance/common/utils.py 2011-01-02 02:47:59 +0000
121+++ glance/common/utils.py 2011-01-11 17:43:19 +0000
122@@ -141,8 +141,7 @@
123
124 def generate_mac():
125 mac = [0x02, 0x16, 0x3e, random.randint(0x00, 0x7f),
126- random.randint(0x00, 0xff), random.randint(0x00, 0xff)
127- ]
128+ random.randint(0x00, 0xff), random.randint(0x00, 0xff)]
129 return ':'.join(map(lambda x: "%02x" % x, mac))
130
131
132
133=== modified file 'glance/registry/client.py'
134--- glance/registry/client.py 2011-01-02 02:26:58 +0000
135+++ glance/registry/client.py 2011-01-11 17:43:19 +0000
136@@ -93,9 +93,13 @@
137 """
138 if 'image' not in image_metadata.keys():
139 image_metadata = dict(image=image_metadata)
140+
141 body = json.dumps(image_metadata)
142- self.do_request("PUT", "/images/%s" % image_id, body)
143- return True
144+
145+ res = self.do_request("PUT", "/images/%s" % image_id, body)
146+ data = json.loads(res.read())
147+ image = data['image']
148+ return image
149
150 def delete_image(self, image_id):
151 """
152
153=== modified file 'glance/registry/db/sqlalchemy/api.py'
154--- glance/registry/db/sqlalchemy/api.py 2011-01-02 02:47:59 +0000
155+++ glance/registry/db/sqlalchemy/api.py 2011-01-11 17:43:19 +0000
156@@ -65,11 +65,11 @@
157 def image_get(context, image_id):
158 session = get_session()
159 try:
160- return session.query(models.Image
161- ).options(joinedload(models.Image.properties)
162- ).filter_by(deleted=_deleted(context)
163- ).filter_by(id=image_id
164- ).one()
165+ return session.query(models.Image).\
166+ options(joinedload(models.Image.properties)).\
167+ filter_by(deleted=_deleted(context)).\
168+ filter_by(id=image_id).\
169+ one()
170 except exc.NoResultFound:
171 new_exc = exception.NotFound("No model for id %s" % image_id)
172 raise new_exc.__class__, new_exc, sys.exc_info()[2]
173@@ -77,19 +77,19 @@
174
175 def image_get_all(context):
176 session = get_session()
177- return session.query(models.Image
178- ).options(joinedload(models.Image.properties)
179- ).filter_by(deleted=_deleted(context)
180- ).all()
181+ return session.query(models.Image).\
182+ options(joinedload(models.Image.properties)).\
183+ filter_by(deleted=_deleted(context)).\
184+ all()
185
186
187 def image_get_all_public(context, public):
188 session = get_session()
189- return session.query(models.Image
190- ).options(joinedload(models.Image.properties)
191- ).filter_by(deleted=_deleted(context)
192- ).filter_by(is_public=public
193- ).all()
194+ return session.query(models.Image).\
195+ options(joinedload(models.Image.properties)).\
196+ filter_by(deleted=_deleted(context)).\
197+ filter_by(is_public=public).\
198+ all()
199
200
201 def image_get_by_str(context, str_id):
202@@ -129,7 +129,9 @@
203 with session.begin():
204 _drop_protected_attrs(models.Image, values)
205
206- values['size'] = int(values['size'])
207+ if 'size' in values:
208+ values['size'] = int(values['size'])
209+
210 values['is_public'] = bool(values.get('is_public', False))
211 properties = values.pop('properties', {})
212
213
214=== modified file 'glance/registry/db/sqlalchemy/models.py'
215--- glance/registry/db/sqlalchemy/models.py 2011-01-02 02:47:59 +0000
216+++ glance/registry/db/sqlalchemy/models.py 2011-01-11 17:43:19 +0000
217@@ -58,18 +58,18 @@
218 """Get all objects of this type"""
219 if not session:
220 session = get_session()
221- return session.query(cls
222- ).filter_by(deleted=deleted
223- ).all()
224+ return session.query(cls).\
225+ filter_by(deleted=deleted).\
226+ all()
227
228 @classmethod
229 def count(cls, session=None, deleted=False):
230 """Count objects of this type"""
231 if not session:
232 session = get_session()
233- return session.query(cls
234- ).filter_by(deleted=deleted
235- ).count()
236+ return session.query(cls).\
237+ filter_by(deleted=deleted).\
238+ count()
239
240 @classmethod
241 def find(cls, obj_id, session=None, deleted=False):
242@@ -77,10 +77,10 @@
243 if not session:
244 session = get_session()
245 try:
246- return session.query(cls
247- ).filter_by(id=obj_id
248- ).filter_by(deleted=deleted
249- ).one()
250+ return session.query(cls).\
251+ filter_by(id=obj_id).\
252+ filter_by(deleted=deleted).\
253+ one()
254 except exc.NoResultFound:
255 new_exc = exception.NotFound("No model for id %s" % obj_id)
256 raise new_exc.__class__, new_exc, sys.exc_info()[2]
257@@ -160,7 +160,7 @@
258
259 @validates('status')
260 def validate_status(self, key, status):
261- if not status in ('available', 'pending', 'disabled'):
262+ if not status in ('active', 'queued', 'killed', 'saving'):
263 raise exception.Invalid("Invalid status '%s' for image." % status)
264 return status
265
266
267=== modified file 'glance/registry/server.py'
268--- glance/registry/server.py 2010-12-26 04:50:01 +0000
269+++ glance/registry/server.py 2011-01-11 17:43:19 +0000
270@@ -106,7 +106,7 @@
271 image_data = json.loads(req.body)['image']
272
273 # Ensure the image has a status set
274- image_data.setdefault('status', 'available')
275+ image_data.setdefault('status', 'active')
276
277 context = None
278 try:
279
280=== modified file 'glance/server.py'
281--- glance/server.py 2010-12-26 04:56:10 +0000
282+++ glance/server.py 2011-01-11 17:43:19 +0000
283@@ -162,17 +162,96 @@
284 util.inject_image_meta_into_headers(res, image)
285 return req.get_response(res)
286
287+ def _reserve(self, req):
288+ image_meta = util.get_image_meta_from_headers(req)
289+ image_meta['status'] = 'queued'
290+
291+ try:
292+ image_meta = registry.add_image_metadata(image_meta)
293+ return image_meta
294+ except exception.Duplicate:
295+ msg = "An image with identifier %s already exists"\
296+ % image_meta['id']
297+ logging.error(msg)
298+ raise HTTPConflict(msg, request=req)
299+ except exception.Invalid:
300+ raise HTTPBadRequest()
301+
302+ def _upload(self, req, image_meta):
303+ content_type = req.headers.get('content-type', 'notset')
304+ if content_type != 'application/octet-stream':
305+ raise HTTPBadRequest(
306+ "Content-Type must be application/octet-stream")
307+
308+ image_store = req.headers.get(
309+ 'x-image-meta-store', FLAGS.default_store)
310+
311+ store = self.get_store_or_400(req, image_store)
312+
313+ image_meta['status'] = 'saving'
314+ registry.update_image_metadata(image_meta['id'], image_meta)
315+
316+ try:
317+ location = store.add(image_meta['id'], req.body)
318+ return location
319+ except exception.Duplicate, e:
320+ logging.error("Error adding image to store: %s", str(e))
321+ raise HTTPConflict(str(e), request=req)
322+
323+ def _activate(self, req, image_meta, location):
324+ image_meta['location'] = location
325+ image_meta['status'] = 'active'
326+ registry.update_image_metadata(image_meta['id'], image_meta)
327+
328+ def _kill(self, req, image_meta):
329+ image_meta['status'] = 'killed'
330+ registry.update_image_metadata(image_meta['id'], image_meta)
331+
332+ def _safe_kill(self, req, image_meta):
333+ """Mark image killed without raising exceptions if it fails.
334+
335+ Since _kill is meant to be called from exceptions handlers, it should
336+ not raise itself, rather it should just log its error.
337+ """
338+ try:
339+ self._kill(req, image_meta)
340+ except Exception, e:
341+ logging.error("Unable to kill image %s: %s",
342+ image_meta['id'], repr(e))
343+
344+ def _upload_and_activate(self, req, image_meta):
345+ try:
346+ location = self._upload(req, image_meta)
347+ self._activate(req, image_meta, location)
348+ except Exception, e:
349+ # NOTE(sirp): _safe_kill uses httplib which, in turn, uses
350+ # Eventlet's GreenSocket. Eventlet subsequently clears exceptions
351+ # by calling `sys.exc_clear()`. This is why we have to `raise e`
352+ # instead of `raise`
353+ self._safe_kill(req, image_meta)
354+ raise e
355+
356 def create(self, req):
357 """
358- Adds a new image to Glance. The body of the request may be a
359- mime-encoded image data. Metadata about the image is sent via
360- HTTP Headers.
361-
362- If the metadata about the image does not include a location
363- to find the image, or if the image location is not valid,
364- the request body *must* be encoded as application/octet-stream
365- and be the image data itself, otherwise an HTTPBadRequest is
366- returned.
367+ Adds a new image to Glance. Three scenarios exist when creating an
368+ image:
369+
370+ 1. If the image data is available for upload, create can be passed the
371+ image data as the request body and the metadata as the request
372+ headers. The image will initially be 'queued', during upload it
373+ will be in the 'saving' status, and then 'killed' or 'active'
374+ depending on whether the upload completed successfully.
375+
376+ 2. If the image data exists somewhere else, you can pass in the source
377+ using the x-image-meta-location header
378+
379+ 3. If the image data is not available yet, but you'd like reserve a
380+ spot for it, you can omit the data and a record will be created in
381+ the 'queued' state. This exists primarily to maintain backwards
382+ compatibility with OpenStack/Rackspace API semantics.
383+
384+ The request body *must* be encoded as application/octet-stream,
385+ otherwise an HTTPBadRequest is returned.
386
387 Upon a successful save of the image data and metadata, a response
388 containing metadata about the image is returned, including its
389@@ -184,62 +263,16 @@
390 and the request body is not application/octet-stream
391 image data.
392 """
393-
394- # Verify the request and headers before we generate a new id
395-
396- image_in_body = False
397- image_store = None
398- header_keys = [k.lower() for k in req.headers.keys()]
399- if 'x-image-meta-location' not in header_keys:
400- if ('content-type' not in header_keys or
401- req.headers['content-type'] != 'application/octet-stream'):
402- raise HTTPBadRequest("Image location was not specified in "
403- "headers and the request body was not "
404- "mime-encoded as application/"
405- "octet-stream.", request=req)
406- else:
407- if 'x-image-meta-store' in header_keys:
408- image_store = req.headers['x-image-meta-store']
409- image_status = 'pending' # set to available when stored...
410- image_in_body = True
411+ image_meta = self._reserve(req)
412+
413+ if req.body:
414+ self._upload_and_activate(req, image_meta)
415 else:
416- image_location = req.headers['x-image-meta-location']
417- image_store = get_store_from_location(image_location)
418- image_status = 'available'
419-
420- # If image is the request body, validate that the requested
421- # or default store is capable of storing the image data...
422- if not image_store:
423- image_store = FLAGS.default_store
424- if image_in_body:
425- store = self.get_store_or_400(req, image_store)
426-
427- image_meta = util.get_image_meta_from_headers(req)
428-
429- image_meta['status'] = image_status
430- image_meta['store'] = image_store
431- try:
432- image_meta = registry.add_image_metadata(image_meta)
433-
434- if image_in_body:
435- try:
436- location = store.add(image_meta['id'], req.body)
437- except exception.Duplicate, e:
438- logging.error("Error adding image to store: %s", str(e))
439- return HTTPConflict(str(e), request=req)
440- image_meta['status'] = 'available'
441- image_meta['location'] = location
442- registry.update_image_metadata(image_meta['id'], image_meta)
443-
444- return dict(image=image_meta)
445-
446- except exception.Duplicate:
447- msg = "An image with identifier %s already exists"\
448- % image_meta['id']
449- logging.error(msg)
450- return HTTPConflict(msg, request=req)
451- except exception.Invalid:
452- return HTTPBadRequest()
453+ if 'x-image-meta-location' in req.headers:
454+ location = req.headers['x-image-meta-location']
455+ self._activate(req, image_meta, location)
456+
457+ return dict(image=image_meta)
458
459 def update(self, req, id):
460 """
461@@ -248,14 +281,21 @@
462 :param request: The WSGI/Webob Request object
463 :param id: The opaque image identifier
464
465- :retval Returns the updated image information as a mapping,
466+ :retval Returns the updated image information as a mapping
467
468 """
469- image = self.get_image_meta_or_404(req, id)
470-
471- image_data = json.loads(req.body)['image']
472- updated_image = registry.update_image_metadata(id, image_data)
473- return dict(image=updated_image)
474+ orig_image_meta = self.get_image_meta_or_404(req, id)
475+ new_image_meta = util.get_image_meta_from_headers(req)
476+
477+ if req.body and (orig_image_meta['status'] != 'queued'):
478+ raise HTTPConflict("Cannot upload to an unqueued image")
479+
480+ image_meta = registry.update_image_metadata(id, new_image_meta)
481+
482+ if req.body:
483+ self._upload_and_activate(req, image_meta)
484+
485+ return dict(image=image_meta)
486
487 def delete(self, req, id):
488 """
489
490=== modified file 'glance/store/__init__.py'
491--- glance/store/__init__.py 2010-12-20 18:38:56 +0000
492+++ glance/store/__init__.py 2011-01-11 17:43:19 +0000
493@@ -56,12 +56,10 @@
494 from glance.store.swift import SwiftBackend
495 from glance.store.filesystem import FilesystemBackend
496
497- BACKENDS = {
498- "file": FilesystemBackend,
499- "http": HTTPBackend,
500- "https": HTTPBackend,
501- "swift": SwiftBackend
502- }
503+ BACKENDS = {"file": FilesystemBackend,
504+ "http": HTTPBackend,
505+ "https": HTTPBackend,
506+ "swift": SwiftBackend}
507
508 try:
509 return BACKENDS[backend]
510
511=== modified file 'glance/store/backends/__init__.py'
512--- glance/store/backends/__init__.py 2011-01-02 02:47:59 +0000
513+++ glance/store/backends/__init__.py 2011-01-11 17:43:19 +0000
514@@ -87,12 +87,10 @@
515 from glance.store.backends.http import HTTPBackend
516 from glance.store.backends.swift import SwiftBackend
517
518- BACKENDS = {
519- "file": FilesystemBackend,
520- "http": HTTPBackend,
521- "https": HTTPBackend,
522- "swift": SwiftBackend
523- }
524+ BACKENDS = {"file": FilesystemBackend,
525+ "http": HTTPBackend,
526+ "https": HTTPBackend,
527+ "swift": SwiftBackend}
528
529 try:
530 return BACKENDS[backend]
531
532=== modified file 'glance/store/filesystem.py'
533--- glance/store/filesystem.py 2011-01-02 02:47:59 +0000
534+++ glance/store/filesystem.py 2011-01-11 17:43:19 +0000
535@@ -114,7 +114,6 @@
536
537 :retval The location that was written, with file:// scheme prepended
538 """
539-
540 datadir = FLAGS.filesystem_store_datadir
541
542 if not os.path.exists(datadir):
543
544=== modified file 'tests/stubs.py'
545--- tests/stubs.py 2011-01-04 22:00:37 +0000
546+++ tests/stubs.py 2011-01-11 17:43:19 +0000
547@@ -279,7 +279,7 @@
548 FIXTURES = [
549 {'id': 1,
550 'name': 'fake image #1',
551- 'status': 'available',
552+ 'status': 'active',
553 'type': 'kernel',
554 'is_public': False,
555 'created_at': datetime.datetime.utcnow(),
556@@ -291,7 +291,7 @@
557 'properties': []},
558 {'id': 2,
559 'name': 'fake image #2',
560- 'status': 'available',
561+ 'status': 'active',
562 'type': 'kernel',
563 'is_public': True,
564 'created_at': datetime.datetime.utcnow(),
565@@ -302,7 +302,7 @@
566 'location': "file:///tmp/glance-tests/2",
567 'properties': []}]
568
569- VALID_STATUSES = ('available', 'disabled', 'pending')
570+ VALID_STATUSES = ('active', 'killed', 'queued', 'saving')
571
572 def __init__(self):
573 self.images = FakeDatastore.FIXTURES
574@@ -317,7 +317,7 @@
575 values['id'])
576
577 if 'status' not in values.keys():
578- values['status'] = 'available'
579+ values['status'] = 'active'
580 else:
581 if not values['status'] in self.VALID_STATUSES:
582 raise exception.Invalid("Invalid status '%s' for image" %
583
584=== modified file 'tests/unit/test_api.py'
585--- tests/unit/test_api.py 2011-01-04 22:00:37 +0000
586+++ tests/unit/test_api.py 2011-01-11 17:43:19 +0000
587@@ -15,6 +15,7 @@
588 # License for the specific language governing permissions and limitations
589 # under the License.
590
591+import httplib
592 import json
593 import unittest
594
595@@ -87,7 +88,7 @@
596 'name': 'fake image #2',
597 'is_public': True,
598 'type': 'kernel',
599- 'status': 'available'
600+ 'status': 'active'
601 }
602 req = webob.Request.blank('/images/detail')
603 res = req.get_response(rserver.API())
604@@ -125,7 +126,7 @@
605 self.assertEquals(3, res_dict['image']['id'])
606
607 # Test status was updated properly
608- self.assertEquals('available', res_dict['image']['status'])
609+ self.assertEquals('active', res_dict['image']['status'])
610
611 def test_create_image_with_bad_status(self):
612 """Tests proper exception is raised if a bad status is set"""
613@@ -251,7 +252,7 @@
614 self.stubs.UnsetAll()
615
616 def test_add_image_no_location_no_image_as_body(self):
617- """Tests raises BadRequest for no body and no loc header"""
618+ """Tests creates a queued image for no body and no loc header"""
619 fixture_headers = {'x-image-meta-store': 'file',
620 'x-image-meta-name': 'fake image #3'}
621
622@@ -260,7 +261,10 @@
623 for k, v in fixture_headers.iteritems():
624 req.headers[k] = v
625 res = req.get_response(server.API())
626- self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
627+ self.assertEquals(res.status_int, httplib.OK)
628+
629+ res_body = json.loads(res.body)['image']
630+ self.assertEquals('queued', res_body['status'])
631
632 def test_add_image_bad_store(self):
633 """Tests raises BadRequest for invalid store header"""
634
635=== modified file 'tests/unit/test_clients.py'
636--- tests/unit/test_clients.py 2011-01-04 22:00:37 +0000
637+++ tests/unit/test_clients.py 2011-01-11 17:43:19 +0000
638@@ -78,7 +78,7 @@
639 'name': 'fake image #2',
640 'is_public': True,
641 'type': 'kernel',
642- 'status': 'available',
643+ 'status': 'active',
644 'size': 19,
645 'location': "file:///tmp/glance-tests/2",
646 'properties': {}}
647@@ -87,7 +87,7 @@
648 'name': 'fake image #2',
649 'is_public': True,
650 'type': 'kernel',
651- 'status': 'available',
652+ 'status': 'active',
653 'size': 19,
654 'location': "file:///tmp/glance-tests/2",
655 'properties': {}}
656@@ -104,7 +104,7 @@
657 'name': 'fake image #2',
658 'is_public': True,
659 'type': 'kernel',
660- 'status': 'available',
661+ 'status': 'active',
662 'size': 19,
663 'location': "file:///tmp/glance-tests/2",
664 'properties': {}}
665@@ -113,7 +113,7 @@
666 'name': 'fake image #2',
667 'is_public': True,
668 'type': 'kernel',
669- 'status': 'available',
670+ 'status': 'active',
671 'size': 19,
672 'location': "file:///tmp/glance-tests/2",
673 'properties': {}}
674@@ -152,7 +152,7 @@
675
676 # Test status was updated properly
677 self.assertTrue('status' in data.keys())
678- self.assertEquals('available', data['status'])
679+ self.assertEquals('active', data['status'])
680
681 def test_add_image_with_properties(self):
682 """Tests that we can add image metadata with properties"""
683@@ -181,7 +181,7 @@
684
685 # Test status was updated properly
686 self.assertTrue('status' in new_image.keys())
687- self.assertEquals('available', new_image['status'])
688+ self.assertEquals('active', new_image['status'])
689
690 def test_add_image_already_exists(self):
691 """Tests proper exception is raised if image with ID already exists"""
692@@ -293,7 +293,7 @@
693 'name': 'fake image #2',
694 'is_public': True,
695 'type': 'kernel',
696- 'status': 'available',
697+ 'status': 'active',
698 'size': 19,
699 'location': "file:///tmp/glance-tests/2",
700 'properties': {}}
701@@ -330,7 +330,7 @@
702 'name': 'fake image #2',
703 'is_public': True,
704 'type': 'kernel',
705- 'status': 'available',
706+ 'status': 'active',
707 'size': 19,
708 'location': "file:///tmp/glance-tests/2",
709 'properties': {}}
710@@ -339,7 +339,7 @@
711 'name': 'fake image #2',
712 'is_public': True,
713 'type': 'kernel',
714- 'status': 'available',
715+ 'status': 'active',
716 'size': 19,
717 'location': "file:///tmp/glance-tests/2",
718 'properties': {}}
719@@ -356,7 +356,7 @@
720 'name': 'fake image #2',
721 'is_public': True,
722 'type': 'kernel',
723- 'status': 'available',
724+ 'status': 'active',
725 'size': 19,
726 'location': "file:///tmp/glance-tests/2",
727 'properties': {}}
728@@ -365,7 +365,7 @@
729 'name': 'fake image #2',
730 'is_public': True,
731 'type': 'kernel',
732- 'status': 'available',
733+ 'status': 'active',
734 'size': 19,
735 'location': "file:///tmp/glance-tests/2",
736 'properties': {}}
737@@ -383,15 +383,14 @@
738 42)
739
740 def test_add_image_without_location_or_raw_data(self):
741- """Tests client throws Invalid if missing both location and raw data"""
742+ """Tests client returns image as queued"""
743 fixture = {'name': 'fake public image',
744 'is_public': True,
745 'type': 'kernel'
746 }
747-
748- self.assertRaises(exception.Invalid,
749- self.client.add_image,
750- fixture)
751+
752+ image_meta = self.client.add_image(fixture)
753+ self.assertEquals('queued', image_meta['status'])
754
755 def test_add_image_basic(self):
756 """Tests that we can add image metadata and returns the new id"""
757@@ -401,11 +400,12 @@
758 'size': 19,
759 'location': "file:///tmp/glance-tests/2",
760 }
761-
762- new_id = self.client.add_image(fixture)
763+
764+ new_image = self.client.add_image(fixture)
765+ new_image_id = new_image['id']
766
767 # Test ID auto-assigned properly
768- self.assertEquals(3, new_id)
769+ self.assertEquals(3, new_image_id)
770
771 # Test all other attributes set
772 data = self.client.get_image_meta(3)
773@@ -415,7 +415,7 @@
774
775 # Test status was updated properly
776 self.assertTrue('status' in data.keys())
777- self.assertEquals('available', data['status'])
778+ self.assertEquals('active', data['status'])
779
780 def test_add_image_with_properties(self):
781 """Tests that we can add image metadata with properties"""
782@@ -433,11 +433,12 @@
783 'location': "file:///tmp/glance-tests/2",
784 'properties': {'distro': 'Ubuntu 10.04 LTS'}
785 }
786-
787- new_id = self.client.add_image(fixture)
788+
789+ new_image = self.client.add_image(fixture)
790+ new_image_id = new_image['id']
791
792 # Test ID auto-assigned properly
793- self.assertEquals(3, new_id)
794+ self.assertEquals(3, new_image_id)
795
796 # Test all other attributes set
797 data = self.client.get_image_meta(3)
798@@ -446,8 +447,8 @@
799 self.assertEquals(v, data[k])
800
801 # Test status was updated properly
802- self.assertTrue('status' in data.keys())
803- self.assertEquals('available', data['status'])
804+ self.assertTrue('status' in data)
805+ self.assertEquals('active', data['status'])
806
807 def test_add_image_already_exists(self):
808 """Tests proper exception is raised if image with ID already exists"""
809@@ -474,11 +475,8 @@
810 'location': "file:///tmp/glance-tests/2",
811 }
812
813- new_id = self.client.add_image(fixture)
814-
815- data = self.client.get_image_meta(new_id)
816-
817- self.assertEquals(data['status'], 'available')
818+ new_image = self.client.add_image(fixture)
819+ self.assertEquals(new_image['status'], 'active')
820
821 def test_add_image_with_image_data_as_string(self):
822 """Tests can add image by passing image data as string"""
823@@ -491,9 +489,9 @@
824
825 image_data_fixture = r"chunk0000remainder"
826
827- new_id = self.client.add_image(fixture, image_data_fixture)
828-
829- self.assertEquals(3, new_id)
830+ new_image = self.client.add_image(fixture, image_data_fixture)
831+ new_image_id = new_image['id']
832+ self.assertEquals(3, new_image_id)
833
834 new_meta, new_image_chunks = self.client.get_image(3)
835
836@@ -525,9 +523,9 @@
837 tmp_file.write(image_data_fixture)
838 tmp_file.close()
839
840- new_id = self.client.add_image(fixture, open(tmp_image_filepath))
841-
842- self.assertEquals(3, new_id)
843+ new_image = self.client.add_image(fixture, open(tmp_image_filepath))
844+ new_image_id = new_image['id']
845+ self.assertEquals(3, new_image_id)
846
847 if os.path.exists(tmp_image_filepath):
848 os.unlink(tmp_image_filepath)
849
850=== modified file 'tests/unit/test_registry_api.py'
851--- tests/unit/test_registry_api.py 2011-01-04 22:00:37 +0000
852+++ tests/unit/test_registry_api.py 2011-01-11 17:43:19 +0000
853@@ -80,7 +80,7 @@
854 'name': 'fake image #2',
855 'is_public': True,
856 'type': 'kernel',
857- 'status': 'available'
858+ 'status': 'active'
859 }
860 req = webob.Request.blank('/images/detail')
861 res = req.get_response(server.API())
862@@ -118,7 +118,7 @@
863 self.assertEquals(3, res_dict['image']['id'])
864
865 # Test status was updated properly
866- self.assertEquals('available', res_dict['image']['status'])
867+ self.assertEquals('active', res_dict['image']['status'])
868
869 def test_create_image_with_bad_status(self):
870 """Tests proper exception is raised if a bad status is set"""

Subscribers

People subscribed via source and target branches