68
69
GET /images/<ID> -- Return image data for image with id <ID>
69
70
POST /images -- Store image data and return metadata about the
71
PUT /images/<ID> -- Update image metadata (not image data, since
72
image data is immutable once stored)
72
PUT /images/<ID> -- Update image metadata and/or upload image
73
data for a previously-reserved image
73
74
DELETE /images/<ID> -- Delete the image with id <ID>
83
84
* id -- The opaque image identifier
84
85
* name -- The name of the image
86
* disk_format -- The disk image format
87
* container_format -- The "container" format of the image
88
* checksum -- MD5 checksum of the image data
85
89
* size -- Size of image data in bytes
87
91
:param request: The WSGI/Webob Request object
167
175
# Using app_iter blanks content-length, so we set it here...
168
176
res.headers.add('Content-Length', image['size'])
169
177
utils.inject_image_meta_into_headers(res, image)
178
res.headers.add('Location', "/images/%s" % id)
179
res.headers.add('ETag', image['checksum'])
170
180
return req.get_response(res)
172
182
def _reserve(self, req):
228
238
store = self.get_store_or_400(req, store_name)
230
image_meta['status'] = 'saving'
231
240
image_id = image_meta['id']
232
logger.debug("Updating image metadata for image %s"
241
logger.debug("Setting image %s to status 'saving'"
234
registry.update_image_metadata(self.options,
243
registry.update_image_metadata(self.options, image_id,
244
{'status': 'saving'})
239
246
logger.debug("Uploading image data for image %(image_id)s "
240
247
"to %(store_name)s store" % locals())
241
location, size = store.add(image_meta['id'],
244
# If size returned from store is different from size
245
# already stored in registry, update the registry with
246
# the new size of the image
247
if image_meta.get('size', 0) != size:
248
image_meta['size'] = size
249
logger.debug("Updating image metadata for image %s"
251
registry.update_image_metadata(self.options,
248
location, size, checksum = store.add(image_meta['id'],
252
# Verify any supplied checksum value matches checksum
253
# returned from store when adding image
254
supplied_checksum = image_meta.get('checksum')
255
if supplied_checksum and supplied_checksum != checksum:
256
msg = ("Supplied checksum (%(supplied_checksum)s) and "
257
"checksum generated from uploaded image "
258
"(%(checksum)s) did not match. Setting image "
259
"status to 'killed'.") % locals()
260
self._safe_kill(req, image_meta)
261
raise HTTPBadRequest(msg, content_type="text/plain",
264
# Update the database with the checksum returned
265
# from the backend store
266
logger.debug("Updating image %(image_id)s data. "
267
"Checksum set to %(checksum)s, size set "
268
"to %(size)d" % locals())
269
registry.update_image_metadata(self.options, image_id,
270
{'checksum': checksum,
255
274
except exception.Duplicate, e:
256
275
logger.error("Error adding image to store: %s", str(e))
257
276
raise HTTPConflict(str(e), request=req)
259
def _activate(self, req, image_meta, location):
278
def _activate(self, req, image_id, location):
261
280
Sets the image status to `active` and the image's location
265
284
:param image_meta: Mapping of metadata about image
266
285
:param location: Location of where Glance stored this image
268
288
image_meta['location'] = location
269
289
image_meta['status'] = 'active'
270
registry.update_image_metadata(self.options,
290
return registry.update_image_metadata(self.options,
274
def _kill(self, req, image_meta):
294
def _kill(self, req, image_id):
276
296
Marks the image status to `killed`
278
298
:param request: The WSGI/Webob Request object
279
:param image_meta: Mapping of metadata about image
299
:param image_id: Opaque image identifier
281
image_meta['status'] = 'killed'
282
301
registry.update_image_metadata(self.options,
303
{'status': 'killed'})
286
def _safe_kill(self, req, image_meta):
305
def _safe_kill(self, req, image_id):
288
307
Mark image killed without raising exceptions if it fails.
291
310
not raise itself, rather it should just log its error.
293
312
:param request: The WSGI/Webob Request object
313
:param image_id: Opaque image identifier
296
self._kill(req, image_meta)
316
self._kill(req, image_id)
297
317
except Exception, e:
298
318
logger.error("Unable to kill image %s: %s",
299
image_meta['id'], repr(e))
301
321
def _upload_and_activate(self, req, image_meta):
307
327
:param request: The WSGI/Webob Request object
308
328
:param image_meta: Mapping of metadata about image
330
:retval Mapping of updated image data
333
image_id = image_meta['id']
311
334
location = self._upload(req, image_meta)
312
self._activate(req, image_meta, location)
335
return self._activate(req, image_id, location)
313
336
except: # unqualified b/c we're re-raising it
314
337
exc_type, exc_value, exc_traceback = sys.exc_info()
315
self._safe_kill(req, image_meta)
338
self._safe_kill(req, image_id)
316
339
# NOTE(sirp): _safe_kill uses httplib which, in turn, uses
317
340
# Eventlet's GreenSocket. Eventlet subsequently clears exceptions
318
341
# by calling `sys.exc_clear()`.
358
381
image_meta = self._reserve(req)
382
image_id = image_meta['id']
360
384
if utils.has_body(req):
361
self._upload_and_activate(req, image_meta)
385
image_meta = self._upload_and_activate(req, image_meta)
363
387
if 'x-image-meta-location' in req.headers:
364
388
location = req.headers['x-image-meta-location']
365
self._activate(req, image_meta, location)
389
image_meta = self._activate(req, image_id, location)
367
391
# APP states we should return a Location: header with the edit
368
392
# URI of the resource newly-created.
369
393
res = Response(request=req, body=json.dumps(dict(image=image_meta)),
370
content_type="text/plain")
371
res.headers.add('Location', "/images/%s" % image_meta['id'])
394
status=httplib.CREATED, content_type="text/plain")
395
res.headers.add('Location', "/images/%s" % image_id)
396
res.headers.add('ETag', image_meta['checksum'])
373
398
return req.get_response(res)
398
self._upload_and_activate(req, image_meta)
423
image_meta = self._upload_and_activate(req, image_meta)
400
return dict(image=image_meta)
425
res = Response(request=req,
426
body=json.dumps(dict(image=image_meta)),
427
content_type="text/plain")
428
res.headers.add('Location', "/images/%s" % id)
429
res.headers.add('ETag', image_meta['checksum'])
401
431
except exception.Invalid, e:
402
432
msg = ("Failed to update image metadata. Got error: %(e)s"