def schedule_delete_from_backend(uri, options, context, image_id, **kwargs): """ Given a uri and a time, schedule the deletion of an image. """ use_delay = config.get_option(options, 'delayed_delete', type='bool', default=False) if not use_delay: registry.update_image_metadata(context, image_id, {'status': 'deleted'}) try: return delete_from_backend(uri, **kwargs) except (UnsupportedBackend, exception.NotFound): msg = _("Failed to delete image from store (%(uri)s).") % locals() logger.error(msg) datadir = config.get_option(options, 'scrubber_datadir') scrub_time = config.get_option(options, 'scrub_time', type='int', default=0) delete_time = time.time() + scrub_time file_path = os.path.join(datadir, str(image_id)) utils.safe_mkdirs(datadir) if os.path.exists(file_path): msg = _("Image id %(image_id)s already queued for delete") % { 'image_id': image_id} raise exception.Duplicate(msg) with open(file_path, 'w') as f: f.write('\n'.join([uri, str(int(delete_time))])) os.chmod(file_path, 0600) os.utime(file_path, (delete_time, delete_time)) registry.update_image_metadata(context, image_id, {'status': 'pending_delete'})
def schedule_delete_from_backend(uri, conf, context, image_id, **kwargs): """ Given a uri and a time, schedule the deletion of an image. """ conf.register_opts(delete_opts) if not conf.delayed_delete: registry.update_image_metadata(context, image_id, {"status": "deleted"}) try: return delete_from_backend(uri, **kwargs) except (UnsupportedBackend, exception.StoreDeleteNotSupported, exception.NotFound): exc_type = sys.exc_info()[0].__name__ msg = _("Failed to delete image at %s from store (%s)") % (uri, exc_type) logger.error(msg) finally: # avoid falling through to the delayed deletion logic return datadir = get_scrubber_datadir(conf) delete_time = time.time() + conf.scrub_time file_path = os.path.join(datadir, str(image_id)) utils.safe_mkdirs(datadir) if os.path.exists(file_path): msg = _("Image id %(image_id)s already queued for delete") % {"image_id": image_id} raise exception.Duplicate(msg) with open(file_path, "w") as f: f.write("\n".join([uri, str(int(delete_time))])) os.chmod(file_path, 0600) os.utime(file_path, (delete_time, delete_time)) registry.update_image_metadata(context, image_id, {"status": "pending_delete"})
def schedule_delete_from_backend(uri, conf, context, image_id, **kwargs): """ Given a uri and a time, schedule the deletion of an image. """ conf.register_opts(delete_opts) if not conf.delayed_delete: registry.update_image_metadata(context, image_id, {'status': 'deleted'}) try: return delete_from_backend(uri, **kwargs) except (UnsupportedBackend, exception.NotFound): msg = _("Failed to delete image from store (%(uri)s).") % locals() logger.error(msg) datadir = get_scrubber_datadir(conf) delete_time = time.time() + conf.scrub_time file_path = os.path.join(datadir, str(image_id)) utils.safe_mkdirs(datadir) if os.path.exists(file_path): msg = _("Image id %(image_id)s already queued for delete") % { 'image_id': image_id } raise exception.Duplicate(msg) with open(file_path, 'w') as f: f.write('\n'.join([uri, str(int(delete_time))])) os.chmod(file_path, 0600) os.utime(file_path, (delete_time, delete_time)) registry.update_image_metadata(context, image_id, {'status': 'pending_delete'})
def _kill(self, req, image_id): """ Marks the image status to `killed`. :param req: The WSGI/Webob Request object :param image_id: Opaque image identifier """ registry.update_image_metadata(req.context, image_id, {"status": "killed"})
def _kill(self, req, image_id): """ Marks the image status to `killed`. :param req: The WSGI/Webob Request object :param image_id: Opaque image identifier """ registry.update_image_metadata(self.options, req.context, image_id, {'status': 'killed'})
def delete(self, req, id): """ Deletes the image and all its chunks from the Glance :param req: The WSGI/Webob Request object :param id: The opaque image identifier :raises HttpBadRequest if image registry is invalid :raises HttpNotFound if image or any chunk is not available :raises HttpUnauthorized if image or any chunk is not deleteable by the requesting user """ self._enforce(req, 'delete_image') image = self.get_image_meta_or_404(req, id) if image['protected']: msg = _("Image is protected") LOG.debug(msg) raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") if image['status'] == 'deleted': msg = _("Forbidden to delete a deleted image.") LOG.debug(msg) raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") if image['location'] and CONF.delayed_delete: status = 'pending_delete' else: status = 'deleted' try: # Delete the image from the registry first, since we rely on it # for authorization checks. # See https://bugs.launchpad.net/glance/+bug/1065187 registry.update_image_metadata(req.context, id, {'status': status}) registry.delete_image_metadata(req.context, id) # The image's location field may be None in the case # of a saving or queued image, therefore don't ask a backend # to delete the image if the backend doesn't yet store it. # See https://bugs.launchpad.net/glance/+bug/747799 if image['location']: if CONF.delayed_delete: schedule_delayed_delete_from_backend(image['location'], id) else: safe_delete_from_backend(image['location'], req.context, id) except exception.NotFound, e: msg = (_("Failed to find image to delete: %(e)s") % locals()) for line in msg.split('\n'): LOG.info(line) raise HTTPNotFound(explanation=msg, request=req, content_type="text/plain")
def delete(self, req, id): """ Deletes the image and all its chunks from the Glance :param req: The WSGI/Webob Request object :param id: The opaque image identifier :raises HttpBadRequest if image registry is invalid :raises HttpNotFound if image or any chunk is not available :raises HttpUnauthorized if image or any chunk is not deleteable by the requesting user """ self._enforce(req, 'delete_image') image = self.get_image_meta_or_404(req, id) if image['protected']: msg = _("Image is protected") LOG.debug(msg) raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") if image['status'] == 'deleted': msg = _("Forbidden to delete a deleted image.") LOG.debug(msg) raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") if image['location'] and CONF.delayed_delete: status = 'pending_delete' else: status = 'deleted' try: # Delete the image from the registry first, since we rely on it # for authorization checks. # See https://bugs.launchpad.net/glance/+bug/1065187 registry.update_image_metadata(req.context, id, {'status': status}) registry.delete_image_metadata(req.context, id) # The image's location field may be None in the case # of a saving or queued image, therefore don't ask a backend # to delete the image if the backend doesn't yet store it. # See https://bugs.launchpad.net/glance/+bug/747799 if image['location']: self._initiate_deletion(req, image['location'], id) except exception.NotFound, e: msg = (_("Failed to find image to delete: %(e)s") % locals()) for line in msg.split('\n'): LOG.info(line) raise HTTPNotFound(explanation=msg, request=req, content_type="text/plain")
def _activate(self, req, image_id, location): """ Sets the image status to `active` and the image's location attribute. :param req: The WSGI/Webob Request object :param image_id: Opaque image identifier :param location: Location of where Glance stored this image """ image_meta = {} image_meta['location'] = location image_meta['status'] = 'active' try: image_meta_data = registry.update_image_metadata(req.context, image_id, image_meta) self.notifier.info("image.activate", redact_loc(image_meta_data)) self.notifier.info("image.update", redact_loc(image_meta_data)) return image_meta_data except exception.Invalid as e: msg = (_("Failed to activate image. Got error: %(e)s") % locals()) LOG.debug(msg) raise HTTPBadRequest(explanation=msg, request=req, content_type="text/plain")
def _activate(self, req, image_id, location): """ Sets the image status to `active` and the image's location attribute. :param req: The WSGI/Webob Request object :param image_id: Opaque image identifier :param location: Location of where Glance stored this image """ image_meta = {} image_meta['location'] = location image_meta['status'] = 'active' try: return registry.update_image_metadata(req.context, image_id, image_meta) except exception.Invalid, e: msg = (_("Failed to activate image. Got error: %(e)s") % locals()) for line in msg.split('\n'): LOG.error(line) self.notifier.error('image.update', msg) raise HTTPBadRequest(explanation=msg, request=req, content_type="text/plain")
def update(self, req, id, image_meta, image_data): """ Updates an existing image with the registry. :param request: The WSGI/Webob Request object :param id: The opaque image identifier :retval Returns the updated image information as a mapping """ self._enforce(req, "modify_image") if image_meta.get("is_public"): self._enforce(req, "publicize_image") if req.context.read_only: msg = _("Read-only access") logger.debug(msg) raise HTTPForbidden(msg, request=req, content_type="text/plain") orig_image_meta = self.get_image_meta_or_404(req, id) orig_status = orig_image_meta["status"] # The default behaviour for a PUT /images/<IMAGE_ID> is to # override any properties that were previously set. This, however, # leads to a number of issues for the common use case where a caller # registers an image with some properties and then almost immediately # uploads an image file along with some more properties. Here, we # check for a special header value to be false in order to force # properties NOT to be purged. However we also disable purging of # properties if an image file is being uploaded... purge_props = req.headers.get("x-glance-registry-purge-props", True) purge_props = utils.bool_from_string(purge_props) and image_data is None if image_data is not None and orig_status != "queued": raise HTTPConflict(_("Cannot upload to an unqueued image")) # Only allow the Location|Copy-From fields to be modified if the # image is in queued status, which indicates that the user called # POST /images but originally supply neither a Location|Copy-From # field NOR image data location = self._external_source(image_meta, req) reactivating = orig_status != "queued" and location activating = orig_status == "queued" and (location or image_data) if reactivating: msg = _("Attempted to update Location field for an image " "not in queued status.") raise HTTPBadRequest(msg, request=req, content_type="text/plain") try: if location: image_meta["size"] = self._get_size(image_meta, location) image_meta = registry.update_image_metadata(req.context, id, image_meta, purge_props) if activating: image_meta = self._handle_source(req, id, image_meta, image_data) except exception.Invalid, e: msg = _("Failed to update image metadata. Got error: %(e)s") % locals() for line in msg.split("\n"): logger.error(line) self.notifier.error("image.update", msg) raise HTTPBadRequest(msg, request=req, content_type="text/plain")
def update(self, req, id, image_meta, image_data): """ Updates an existing image with the registry. :param request: The WSGI/Webob Request object :param id: The opaque image identifier :retval Returns the updated image information as a mapping """ if req.context.read_only: msg = _("Read-only access") logger.debug(msg) raise HTTPForbidden(msg, request=req, content_type="text/plain") orig_image_meta = self.get_image_meta_or_404(req, id) orig_status = orig_image_meta['status'] if image_data is not None and orig_status != 'queued': raise HTTPConflict(_("Cannot upload to an unqueued image")) try: image_meta = registry.update_image_metadata( self.options, req.context, id, image_meta, True) if image_data is not None: image_meta = self._upload_and_activate(req, image_meta) except exception.Invalid, e: msg = (_("Failed to update image metadata. Got error: %(e)s") % locals()) for line in msg.split('\n'): logger.error(line) self.notifier.error('image.update', msg) raise HTTPBadRequest(msg, request=req, content_type="text/plain")
def update(self, req, id, image_meta, image_data): """ Updates an existing image with the registry. :param request: The WSGI/Webob Request object :param id: The opaque image identifier :retval Returns the updated image information as a mapping """ if req.context.read_only: msg = "Read-only access" logger.debug(msg) raise HTTPForbidden(msg, request=req, content_type="text/plain") orig_image_meta = self.get_image_meta_or_404(req, id) orig_status = orig_image_meta['status'] if image_data is not None and orig_status != 'queued': raise HTTPConflict("Cannot upload to an unqueued image") try: image_meta = registry.update_image_metadata(self.options, req.context, id, image_meta, True) if image_data is not None: image_meta = self._upload_and_activate(req, image_meta) except exception.Invalid, e: msg = ("Failed to update image metadata. Got error: %(e)s" % locals()) for line in msg.split('\n'): logger.error(line) raise HTTPBadRequest(msg, request=req, content_type="text/plain")
def schedule_delete_from_backend(uri, options, context, image_id, **kwargs): """ Given a uri and a time, schedule the deletion of an image. """ use_delay = config.get_option(options, 'delayed_delete', type='bool', default=False) if not use_delay: registry.update_image_metadata(options, context, image_id, {'status': 'deleted'}) try: return delete_from_backend(uri, **kwargs) except (UnsupportedBackend, exception.NotFound): msg = _("Failed to delete image from store (%(uri)s).") % locals() logger.error(msg) registry.update_image_metadata(options, context, image_id, {'status': 'pending_delete'})
def schedule_delete_from_backend(uri, options, context, id, **kwargs): """ Given a uri and a time, schedule the deletion of an image. """ use_delay = config.get_option(options, 'delayed_delete', type='bool', default=False) if not use_delay: registry.update_image_metadata(options, context, id, {'status': 'deleted'}) try: return delete_from_backend(uri, **kwargs) except (UnsupportedBackend, exception.NotFound): msg = "Failed to delete image from store (%s). " logger.error(msg % uri) registry.update_image_metadata(options, context, id, {'status': 'pending_delete'})
def update(self, req, id, image_meta, image_data): """ Updates an existing image with the registry. :param request: The WSGI/Webob Request object :param id: The opaque image identifier :retval Returns the updated image information as a mapping """ self._enforce(req, 'modify_image') if req.context.read_only: msg = _("Read-only access") logger.debug(msg) raise HTTPForbidden(msg, request=req, content_type="text/plain") orig_image_meta = self.get_image_meta_or_404(req, id) orig_status = orig_image_meta['status'] # The default behaviour for a PUT /images/<IMAGE_ID> is to # override any properties that were previously set. This, however, # leads to a number of issues for the common use case where a caller # registers an image with some properties and then almost immediately # uploads an image file along with some more properties. Here, we # check for a special header value to be false in order to force # properties NOT to be purged. However we also disable purging of # properties if an image file is being uploaded... purge_props = req.headers.get('x-glance-registry-purge-props', True) purge_props = (utils.bool_from_string(purge_props) and image_data is None) if image_data is not None and orig_status != 'queued': raise HTTPConflict(_("Cannot upload to an unqueued image")) # Only allow the Location fields to be modified if the image is # in queued status, which indicates that the user called POST /images # but did not supply either a Location field OR image data if not orig_status == 'queued' and 'location' in image_meta: msg = _("Attempted to update Location field for an image " "not in queued status.") raise HTTPBadRequest(msg, request=req, content_type="text/plain") try: image_meta = registry.update_image_metadata(req.context, id, image_meta, purge_props) if image_data is not None: image_meta = self._upload_and_activate(req, image_meta) except exception.Invalid, e: msg = (_("Failed to update image metadata. Got error: %(e)s") % locals()) for line in msg.split('\n'): logger.error(line) self.notifier.error('image.update', msg) raise HTTPBadRequest(msg, request=req, content_type="text/plain")
def _activate(self, req, image_id, location): """ Sets the image status to `active` and the image's location attribute. :param req: The WSGI/Webob Request object :param image_id: Opaque image identifier :param location: Location of where Glance stored this image """ image_meta = {} image_meta["location"] = location image_meta["status"] = "active" return registry.update_image_metadata(self.options, req.context, image_id, image_meta)
def _activate(self, req, image_id, location): """ Sets the image status to `active` and the image's location attribute. :param req: The WSGI/Webob Request object :param image_id: Opaque image identifier :param location: Location of where Glance stored this image """ image_meta = {} image_meta['location'] = location image_meta['status'] = 'active' return registry.update_image_metadata(self.options, req.context, image_id, image_meta)
def schedule_delete_from_backend(uri, context, image_id, **kwargs): """ Given a uri and a time, schedule the deletion of an image. """ if not CONF.delayed_delete: registry.update_image_metadata(context, image_id, {'status': 'deleted'}) try: return delete_from_backend(context, uri, **kwargs) except (UnsupportedBackend, exception.StoreDeleteNotSupported, exception.NotFound): exc_type = sys.exc_info()[0].__name__ msg = (_("Failed to delete image at %s from store (%s)") % (uri, exc_type)) LOG.error(msg) finally: # avoid falling through to the delayed deletion logic return datadir = CONF.scrubber_datadir delete_time = time.time() + CONF.scrub_time file_path = os.path.join(datadir, str(image_id)) utils.safe_mkdirs(datadir) if os.path.exists(file_path): msg = _("Image id %(image_id)s already queued for delete") % { 'image_id': image_id } raise exception.Duplicate(msg) with open(file_path, 'w') as f: f.write('\n'.join([uri, str(int(delete_time))])) os.chmod(file_path, 0600) os.utime(file_path, (delete_time, delete_time)) registry.update_image_metadata(context, image_id, {'status': 'pending_delete'})
def _activate(self, req, image_id, location): """ Sets the image status to `active` and the image's location attribute. :param req: The WSGI/Webob Request object :param image_id: Opaque image identifier :param location: Location of where Glance stored this image """ image_meta = {} image_meta['location'] = location image_meta['status'] = 'active' try: return registry.update_image_metadata(req.context, image_id, image_meta) except exception.Invalid, e: msg = (_("Failed to activate image. Got error: %(e)s") % locals()) for line in msg.split('\n'): logger.error(line) self.notifier.error('image.update', msg) raise HTTPBadRequest(msg, request=req, content_type="text/plain")
def update(self, req, id, image_meta, image_data): """ Updates an existing image with the registry. :param request: The WSGI/Webob Request object :param id: The opaque image identifier :retval Returns the updated image information as a mapping """ self._enforce(req, 'modify_image') is_public = image_meta.get('is_public') if is_public: self._enforce(req, 'publicize_image') orig_image_meta = self.get_image_meta_or_404(req, id) orig_status = orig_image_meta['status'] # Do not allow any updates on a deleted image. # Fix for LP Bug #1060930 if orig_status == 'deleted': msg = _("Forbidden to update deleted image.") raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") # The default behaviour for a PUT /images/<IMAGE_ID> is to # override any properties that were previously set. This, however, # leads to a number of issues for the common use case where a caller # registers an image with some properties and then almost immediately # uploads an image file along with some more properties. Here, we # check for a special header value to be false in order to force # properties NOT to be purged. However we also disable purging of # properties if an image file is being uploaded... purge_props = req.headers.get('x-glance-registry-purge-props', True) purge_props = (utils.bool_from_string(purge_props) and image_data is None) if image_data is not None and orig_status != 'queued': raise HTTPConflict(_("Cannot upload to an unqueued image")) # Only allow the Location|Copy-From fields to be modified if the # image is in queued status, which indicates that the user called # POST /images but originally supply neither a Location|Copy-From # field NOR image data location = self._external_source(image_meta, req) reactivating = orig_status != 'queued' and location activating = orig_status == 'queued' and (location or image_data) # Make image public in the backend store (if implemented) orig_or_updated_loc = location or orig_image_meta.get('location', None) if orig_or_updated_loc: self.update_store_acls(req, id, orig_or_updated_loc, public=is_public) if reactivating: msg = _("Attempted to update Location field for an image " "not in queued status.") raise HTTPBadRequest(explanation=msg, request=req, content_type="text/plain") try: if location: image_meta['size'] = self._get_size(req.context, image_meta, location) image_meta = registry.update_image_metadata(req.context, id, image_meta, purge_props) if activating: image_meta = self._handle_source(req, id, image_meta, image_data) except exception.Invalid as e: msg = (_("Failed to update image metadata. Got error: %(e)s") % locals()) LOG.debug(msg) raise HTTPBadRequest(explanation=msg, request=req, content_type="text/plain") except exception.NotFound as e: msg = (_("Failed to find image to update: %(e)s") % locals()) for line in msg.split('\n'): LOG.info(line) raise HTTPNotFound(explanation=msg, request=req, content_type="text/plain") except exception.Forbidden as e: msg = (_("Forbidden to update image: %(e)s") % locals()) for line in msg.split('\n'): LOG.info(line) raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") else: self.notifier.info('image.update', redact_loc(image_meta)) # Prevent client from learning the location, as it # could contain security credentials image_meta.pop('location', None) return {'image_meta': image_meta}
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that store, if not, Glance will use the store set by the flag `default_store`. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ try: req.get_content_type('application/octet-stream') except exception.InvalidContentType: self._safe_kill(req, image_meta['id']) msg = "Content-Type must be application/octet-stream" logger.error(msg) raise HTTPBadRequest(explanation=msg) store_name = req.headers.get('x-image-meta-store', self.options['default_store']) store = self.get_store_or_400(req, store_name) image_id = image_meta['id'] logger.debug("Setting image %s to status 'saving'", image_id) registry.update_image_metadata(self.options, req.context, image_id, {'status': 'saving'}) try: logger.debug("Uploading image data for image %(image_id)s " "to %(store_name)s store", locals()) req.make_body_seekable() location, size, checksum = store.add(image_meta['id'], req.body_file, self.options) # Verify any supplied checksum value matches checksum # returned from store when adding image supplied_checksum = image_meta.get('checksum') if supplied_checksum and supplied_checksum != checksum: msg = ("Supplied checksum (%(supplied_checksum)s) and " "checksum generated from uploaded image " "(%(checksum)s) did not match. Setting image " "status to 'killed'.") % locals() logger.error(msg) self._safe_kill(req, image_id) raise HTTPBadRequest(msg, content_type="text/plain", request=req) # Update the database with the checksum returned # from the backend store logger.debug("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d", locals()) registry.update_image_metadata(self.options, req.context, image_id, {'checksum': checksum, 'size': size}) return location except exception.Duplicate, e: msg = ("Attempt to upload duplicate image: %s") % str(e) logger.error(msg) self._safe_kill(req, image_id) raise HTTPConflict(msg, request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that store, if not, Glance will use the store set by the flag `default_store`. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ try: req.get_content_type("application/octet-stream") except exception.InvalidContentType: self._safe_kill(req, image_meta["id"]) msg = _("Content-Type must be application/octet-stream") logger.error(msg) raise HTTPBadRequest(explanation=msg) store_name = req.headers.get("x-image-meta-store", self.options["default_store"]) store = self.get_store_or_400(req, store_name) image_id = image_meta["id"] logger.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(self.options, req.context, image_id, {"status": "saving"}) try: logger.debug(_("Uploading image data for image %(image_id)s " "to %(store_name)s store"), locals()) if req.content_length: image_size = int(req.content_length) elif "x-image-meta-size" in req.headers: image_size = int(req.headers["x-image-meta-size"]) else: logger.debug(_("Got request with no content-length and no " "x-image-meta-size header")) image_size = 0 location, size, checksum = store.add(image_meta["id"], req.body_file, image_size) # Verify any supplied checksum value matches checksum # returned from store when adding image supplied_checksum = image_meta.get("checksum") if supplied_checksum and supplied_checksum != checksum: msg = ( _( "Supplied checksum (%(supplied_checksum)s) and " "checksum generated from uploaded image " "(%(checksum)s) did not match. Setting image " "status to 'killed'." ) % locals() ) logger.error(msg) self._safe_kill(req, image_id) raise HTTPBadRequest(msg, content_type="text/plain", request=req) # Update the database with the checksum returned # from the backend store logger.debug( _("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals(), ) registry.update_image_metadata(self.options, req.context, image_id, {"checksum": checksum, "size": size}) self.notifier.info("image.upload", image_meta) return location except exception.Duplicate, e: msg = _("Attempt to upload duplicate image: %s") % e logger.error(msg) self._safe_kill(req, image_id) self.notifier.error("image.upload", msg) raise HTTPConflict(msg, request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that scheme; if not, Glance will use the scheme set by the flag `default_store` to find the backing store. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ copy_from = self._copy_from(req) if copy_from: try: image_data, image_size = self._get_from_store(req.context, copy_from) except Exception as e: self._safe_kill(req, image_meta["id"]) msg = _("Copy from external source failed: %s") % e LOG.debug(msg) return image_meta["size"] = image_size or image_meta["size"] else: try: req.get_content_type("application/octet-stream") except exception.InvalidContentType: self._safe_kill(req, image_meta["id"]) msg = _("Content-Type must be application/octet-stream") LOG.debug(msg) raise HTTPBadRequest(explanation=msg) image_data = req.body_file scheme = req.headers.get("x-image-meta-store", CONF.default_store) store = self.get_store_or_400(req, scheme) image_id = image_meta["id"] LOG.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(req.context, image_id, {"status": "saving"}) LOG.debug(_("Uploading image data for image %(image_id)s " "to %(scheme)s store"), locals()) try: self.notifier.info("image.prepare", image_meta) location, size, checksum = store.add( image_meta["id"], utils.CooperativeReader(image_data), image_meta["size"] ) def _kill_mismatched(image_meta, attr, actual): supplied = image_meta.get(attr) if supplied and supplied != actual: msg = ( _( "Supplied %(attr)s (%(supplied)s) and " "%(attr)s generated from uploaded image " "(%(actual)s) did not match. Setting image " "status to 'killed'." ) % locals() ) LOG.error(msg) self._safe_kill(req, image_id) self._initiate_deletion(req, location, image_id) raise HTTPBadRequest(explanation=msg, content_type="text/plain", request=req) # Verify any supplied size/checksum value matches size/checksum # returned from store when adding image _kill_mismatched(image_meta, "size", size) _kill_mismatched(image_meta, "checksum", checksum) # Update the database with the checksum returned # from the backend store LOG.debug( _("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals(), ) update_data = {"checksum": checksum, "size": size} image_meta = registry.update_image_metadata(req.context, image_id, update_data) self.notifier.info("image.upload", image_meta) return location except exception.Duplicate, e: msg = _("Attempt to upload duplicate image: %s") % e LOG.debug(msg) self._safe_kill(req, image_id) raise HTTPConflict(explanation=msg, request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that store, if not, Glance will use the store set by the flag `default_store`. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ try: req.get_content_type('application/octet-stream') except exception.InvalidContentType: self._safe_kill(req, image_meta['id']) msg = _("Content-Type must be application/octet-stream") logger.error(msg) raise HTTPBadRequest(explanation=msg) store_name = req.headers.get('x-image-meta-store', self.options['default_store']) store = self.get_store_or_400(req, store_name) image_id = image_meta['id'] logger.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(self.options, req.context, image_id, {'status': 'saving'}) try: logger.debug( _("Uploading image data for image %(image_id)s " "to %(store_name)s store"), locals()) if req.content_length: image_size = int(req.content_length) elif 'x-image-meta-size' in req.headers: image_size = int(req.headers['x-image-meta-size']) else: logger.debug( _("Got request with no content-length and no " "x-image-meta-size header")) image_size = 0 location, size, checksum = store.add(image_meta['id'], req.body_file, image_size) # Verify any supplied checksum value matches checksum # returned from store when adding image supplied_checksum = image_meta.get('checksum') if supplied_checksum and supplied_checksum != checksum: msg = _("Supplied checksum (%(supplied_checksum)s) and " "checksum generated from uploaded image " "(%(checksum)s) did not match. Setting image " "status to 'killed'.") % locals() logger.error(msg) self._safe_kill(req, image_id) raise HTTPBadRequest(msg, content_type="text/plain", request=req) # Update the database with the checksum returned # from the backend store logger.debug( _("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals()) registry.update_image_metadata(self.options, req.context, image_id, { 'checksum': checksum, 'size': size }) self.notifier.info('image.upload', image_meta) return location except exception.Duplicate, e: msg = _("Attempt to upload duplicate image: %s") % e logger.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPConflict(msg, request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that scheme; if not, Glance will use the scheme set by the flag `default_store` to find the backing store. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ copy_from = self._copy_from(req) if copy_from: image_data, image_size = self._get_from_store( req.context, copy_from) image_meta['size'] = image_size or image_meta['size'] else: try: req.get_content_type('application/octet-stream') except exception.InvalidContentType: self._safe_kill(req, image_meta['id']) msg = _("Content-Type must be application/octet-stream") LOG.error(msg) raise HTTPBadRequest(explanation=msg) image_data = req.body_file scheme = req.headers.get('x-image-meta-store', CONF.default_store) store = self.get_store_or_400(req, scheme) image_id = image_meta['id'] LOG.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(req.context, image_id, {'status': 'saving'}) LOG.debug( _("Uploading image data for image %(image_id)s " "to %(scheme)s store"), locals()) try: location, size, checksum = store.add( image_meta['id'], utils.CooperativeReader(image_data), image_meta['size']) # Verify any supplied checksum value matches checksum # returned from store when adding image supplied_checksum = image_meta.get('checksum') if supplied_checksum and supplied_checksum != checksum: msg = _("Supplied checksum (%(supplied_checksum)s) and " "checksum generated from uploaded image " "(%(checksum)s) did not match. Setting image " "status to 'killed'.") % locals() LOG.error(msg) self._safe_kill(req, image_id) raise HTTPBadRequest(explanation=msg, content_type="text/plain", request=req) # Update the database with the checksum returned # from the backend store LOG.debug( _("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals()) update_data = {'checksum': checksum, 'size': size} image_meta = registry.update_image_metadata( req.context, image_id, update_data) self.notifier.info('image.upload', image_meta) return location except exception.Duplicate, e: msg = _("Attempt to upload duplicate image: %s") % e LOG.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPConflict(explanation=msg, request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that scheme; if not, Glance will use the scheme set by the flag `default_store` to find the backing store. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ copy_from = self._copy_from(req) if copy_from: try: image_data, image_size = self._get_from_store(req.context, copy_from) except Exception as e: self._safe_kill(req, image_meta['id']) msg = _("Copy from external source failed: %s") % e LOG.debug(msg) return image_meta['size'] = image_size or image_meta['size'] else: try: req.get_content_type('application/octet-stream') except exception.InvalidContentType: self._safe_kill(req, image_meta['id']) msg = _("Content-Type must be application/octet-stream") LOG.debug(msg) raise HTTPBadRequest(explanation=msg) image_data = req.body_file scheme = req.headers.get('x-image-meta-store', CONF.default_store) store = self.get_store_or_400(req, scheme) image_id = image_meta['id'] LOG.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(req.context, image_id, {'status': 'saving'}) LOG.debug(_("Uploading image data for image %(image_id)s " "to %(scheme)s store"), locals()) try: self.notifier.info("image.prepare", redact_loc(image_meta)) location, size, checksum = store.add( image_meta['id'], utils.CooperativeReader(image_data), image_meta['size']) def _kill_mismatched(image_meta, attr, actual): supplied = image_meta.get(attr) if supplied and supplied != actual: msg = _("Supplied %(attr)s (%(supplied)s) and " "%(attr)s generated from uploaded image " "(%(actual)s) did not match. Setting image " "status to 'killed'.") % locals() LOG.error(msg) self._safe_kill(req, image_id) self._initiate_deletion(req, location, image_id) raise HTTPBadRequest(explanation=msg, content_type="text/plain", request=req) # Verify any supplied size/checksum value matches size/checksum # returned from store when adding image _kill_mismatched(image_meta, 'size', size) _kill_mismatched(image_meta, 'checksum', checksum) # Update the database with the checksum returned # from the backend store LOG.debug(_("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals()) update_data = {'checksum': checksum, 'size': size} image_meta = registry.update_image_metadata(req.context, image_id, update_data) self.notifier.info('image.upload', redact_loc(image_meta)) return location except exception.Duplicate as e: msg = _("Attempt to upload duplicate image: %s") % e LOG.debug(msg) self._safe_kill(req, image_id) raise HTTPConflict(explanation=msg, request=req) except exception.Forbidden as e: msg = _("Forbidden upload attempt: %s") % e LOG.debug(msg) self._safe_kill(req, image_id) raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") except exception.StorageFull as e: msg = _("Image storage media is full: %s") % e LOG.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPRequestEntityTooLarge(explanation=msg, request=req, content_type='text/plain') except exception.StorageWriteDenied as e: msg = _("Insufficient permissions on image storage media: %s") % e LOG.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPServiceUnavailable(explanation=msg, request=req, content_type='text/plain') except exception.ImageSizeLimitExceeded as e: msg = _("Denying attempt to upload image larger than %d bytes." % CONF.image_size_cap) LOG.info(msg) self._safe_kill(req, image_id) raise HTTPBadRequest(explanation=msg, request=req, content_type='text/plain') except HTTPError as e: self._safe_kill(req, image_id) #NOTE(bcwaldon): Ideally, we would just call 'raise' here, # but something in the above function calls is affecting the # exception context and we must explicitly re-raise the # caught exception. raise e except Exception as e: LOG.exception(_("Failed to upload image")) self._safe_kill(req, image_id) raise HTTPInternalServerError(request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that scheme; if not, Glance will use the scheme set by the flag `default_store` to find the backing store. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ copy_from = self._copy_from(req) if copy_from: image_data, image_size = self._get_from_store(req.context, copy_from) image_meta['size'] = image_size or image_meta['size'] else: try: req.get_content_type('application/octet-stream') except exception.InvalidContentType: self._safe_kill(req, image_meta['id']) msg = _("Content-Type must be application/octet-stream") LOG.error(msg) raise HTTPBadRequest(explanation=msg) image_data = req.body_file scheme = req.headers.get('x-image-meta-store', CONF.default_store) store = self.get_store_or_400(req, scheme) image_id = image_meta['id'] LOG.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(req.context, image_id, {'status': 'saving'}) LOG.debug(_("Uploading image data for image %(image_id)s " "to %(scheme)s store"), locals()) try: location, size, checksum = store.add( image_meta['id'], utils.CooperativeReader(image_data), image_meta['size']) # Verify any supplied checksum value matches checksum # returned from store when adding image supplied_checksum = image_meta.get('checksum') if supplied_checksum and supplied_checksum != checksum: msg = _("Supplied checksum (%(supplied_checksum)s) and " "checksum generated from uploaded image " "(%(checksum)s) did not match. Setting image " "status to 'killed'.") % locals() LOG.error(msg) self._safe_kill(req, image_id) raise HTTPBadRequest(explanation=msg, content_type="text/plain", request=req) # Update the database with the checksum returned # from the backend store LOG.debug(_("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals()) update_data = {'checksum': checksum, 'size': size} image_meta = registry.update_image_metadata(req.context, image_id, update_data) self.notifier.info('image.upload', image_meta) return location except exception.Duplicate, e: msg = _("Attempt to upload duplicate image: %s") % e LOG.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPConflict(explanation=msg, request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that scheme; if not, Glance will use the scheme set by the flag `default_store` to find the backing store. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ copy_from = self._copy_from(req) if copy_from: try: image_data, image_size = self._get_from_store(req.context, copy_from) except Exception as e: self._safe_kill(req, image_meta['id']) msg = _("Copy from external source failed: %s") % e LOG.debug(msg) return image_meta['size'] = image_size or image_meta['size'] else: try: req.get_content_type('application/octet-stream') except exception.InvalidContentType: self._safe_kill(req, image_meta['id']) msg = _("Content-Type must be application/octet-stream") LOG.debug(msg) raise HTTPBadRequest(explanation=msg) image_data = req.body_file scheme = req.headers.get('x-image-meta-store', CONF.default_store) store = self.get_store_or_400(req, scheme) image_id = image_meta['id'] LOG.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(req.context, image_id, {'status': 'saving'}) LOG.debug(_("Uploading image data for image %(image_id)s " "to %(scheme)s store"), locals()) try: self.notifier.info("image.prepare", redact_loc(image_meta)) location, size, checksum = store.add( image_meta['id'], utils.CooperativeReader(image_data), image_meta['size']) def _kill_mismatched(image_meta, attr, actual): supplied = image_meta.get(attr) if supplied and supplied != actual: msg = _("Supplied %(attr)s (%(supplied)s) and " "%(attr)s generated from uploaded image " "(%(actual)s) did not match. Setting image " "status to 'killed'.") % locals() LOG.error(msg) self._safe_kill(req, image_id) self._initiate_deletion(req, location, image_id) raise HTTPBadRequest(explanation=msg, content_type="text/plain", request=req) # Verify any supplied size/checksum value matches size/checksum # returned from store when adding image _kill_mismatched(image_meta, 'size', size) _kill_mismatched(image_meta, 'checksum', checksum) # Update the database with the checksum returned # from the backend store LOG.debug(_("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals()) update_data = {'checksum': checksum, 'size': size} image_meta = registry.update_image_metadata(req.context, image_id, update_data) self.notifier.info('image.upload', redact_loc(image_meta)) return location except exception.Duplicate as e: msg = _("Attempt to upload duplicate image: %s") % e LOG.debug(msg) self._safe_kill(req, image_id) raise HTTPConflict(explanation=msg, request=req) except exception.Forbidden as e: msg = _("Forbidden upload attempt: %s") % e LOG.debug(msg) self._safe_kill(req, image_id) raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain") except exception.StorageFull as e: msg = _("Image storage media is full: %s") % e LOG.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPRequestEntityTooLarge(explanation=msg, request=req, content_type='text/plain') except exception.StorageWriteDenied as e: msg = _("Insufficient permissions on image storage media: %s") % e LOG.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPServiceUnavailable(explanation=msg, request=req, content_type='text/plain') except exception.ImageSizeLimitExceeded as e: msg = _("Denying attempt to upload image larger than %d bytes." % CONF.image_size_cap) LOG.info(msg) self._safe_kill(req, image_id) raise HTTPRequestEntityTooLarge(explanation=msg, request=req, content_type='text/plain') except HTTPError as e: self._safe_kill(req, image_id) #NOTE(bcwaldon): Ideally, we would just call 'raise' here, # but something in the above function calls is affecting the # exception context and we must explicitly re-raise the # caught exception. raise e except Exception as e: LOG.exception(_("Failed to upload image")) self._safe_kill(req, image_id) raise HTTPInternalServerError(request=req)
def _upload(self, req, image_meta): """ Uploads the payload of the request to a backend store in Glance. If the `x-image-meta-store` header is set, Glance will attempt to use that store, if not, Glance will use the store set by the flag `default_store`. :param req: The WSGI/Webob Request object :param image_meta: Mapping of metadata about image :raises HTTPConflict if image already exists :retval The location where the image was stored """ copy_from = self._copy_from(req) if copy_from: image_data, image_size = self._get_from_store(copy_from) image_meta['size'] = image_size or image_meta['size'] else: try: req.get_content_type('application/octet-stream') except exception.InvalidContentType: self._safe_kill(req, image_meta['id']) msg = _("Content-Type must be application/octet-stream") logger.error(msg) raise HTTPBadRequest(explanation=msg) image_data = req.body_file if req.content_length: image_size = int(req.content_length) elif 'x-image-meta-size' in req.headers: image_size = int(req.headers['x-image-meta-size']) else: logger.debug(_("Got request with no content-length and no " "x-image-meta-size header")) image_size = 0 store_name = req.headers.get('x-image-meta-store', self.conf.default_store) store = self.get_store_or_400(req, store_name) image_id = image_meta['id'] logger.debug(_("Setting image %s to status 'saving'"), image_id) registry.update_image_metadata(req.context, image_id, {'status': 'saving'}) try: logger.debug(_("Uploading image data for image %(image_id)s " "to %(store_name)s store"), locals()) if image_size > IMAGE_SIZE_CAP: max_image_size = IMAGE_SIZE_CAP msg = _("Denying attempt to upload image larger than " "%(max_image_size)d. Supplied image size was " "%(image_size)d") % locals() logger.warn(msg) raise HTTPBadRequest(msg, request=req) location, size, checksum = store.add(image_meta['id'], image_data, image_size) # Verify any supplied checksum value matches checksum # returned from store when adding image supplied_checksum = image_meta.get('checksum') if supplied_checksum and supplied_checksum != checksum: msg = _("Supplied checksum (%(supplied_checksum)s) and " "checksum generated from uploaded image " "(%(checksum)s) did not match. Setting image " "status to 'killed'.") % locals() logger.error(msg) self._safe_kill(req, image_id) raise HTTPBadRequest(msg, content_type="text/plain", request=req) # Update the database with the checksum returned # from the backend store logger.debug(_("Updating image %(image_id)s data. " "Checksum set to %(checksum)s, size set " "to %(size)d"), locals()) registry.update_image_metadata(req.context, image_id, {'checksum': checksum, 'size': size}) self.notifier.info('image.upload', image_meta) return location except exception.Duplicate, e: msg = _("Attempt to upload duplicate image: %s") % e logger.error(msg) self._safe_kill(req, image_id) self.notifier.error('image.upload', msg) raise HTTPConflict(msg, request=req)