API server: 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') if req.context.read_only: msg = _("Read-only access") logger.debug(msg) raise HTTPForbidden(msg, request=req, content_type="text/plain") image = self.get_image_meta_or_404(req, id) if image['protected']: msg = _("Image is protected") logger.debug(msg) raise HTTPForbidden(msg, request=req, content_type="text/plain") # 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 try: if image['location']: schedule_delete_from_backend(image['location'], self.conf, req.context, id) -- 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'}) <<<< will fail if delayed delete switched off (good) 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)) <<<< will successfully schedule delete (bad) registry.update_image_metadata(context, image_id, {'status': 'pending_delete'}) -- def update_image_metadata(context, image_id, image_meta, purge_props=False): logger.debug(_("Updating image metadata for image %s..."), image_id) c = get_registry_client(context) return c.update_image(image_id, image_meta, purge_props) -- def update_image(self, image_id, image_metadata, purge_props=False): """ Updates Registry's information about an image """ if 'image' not in image_metadata.keys(): image_metadata = dict(image=image_metadata) image_metadata['image'] = self.encrypt_metadata( image_metadata['image']) body = json.dumps(image_metadata) headers = { 'Content-Type': 'application/json', } if purge_props: headers["X-Glance-Registry-Purge-Props"] = "true" res = self.do_request("PUT", "/images/%s" % image_id, body, headers) -- Registry server: def update(self, req, id, body): """ Updates an existing image with the registry. :param req: wsgi Request object :param body: Dictionary of information about the image :param id: The opaque internal identifier for the image :retval Returns the updated image information as a mapping, """ if req.context.read_only: raise exc.HTTPForbidden() image_data = body['image'] # Prohibit modification of 'owner' if not req.context.is_admin and 'owner' in image_data: del image_data['owner'] purge_props = req.headers.get("X-Glance-Registry-Purge-Props", "false") try: logger.debug(_("Updating image %(id)s with metadata: " "%(image_data)r") % locals()) if purge_props == "true": updated_image = db_api.image_update(req.context, id, image_data, True) else: updated_image = db_api.image_update(req.context, id, image_data) -- def image_update(context, image_id, values, purge_props=False): """ Set the given properties on an image and update it. :raises NotFound if image does not exist. """ return _image_update(context, values, image_id, purge_props) -- def _image_update(context, values, image_id, purge_props=False): """ Used internally by image_create and image_update :param context: Request context :param values: A dict of attributes to set :param image_id: If None, create the image, otherwise, find and update it """ session = get_session() with session.begin(): # Remove the properties passed in the values mapping. We # handle properties separately from base image attributes, # and leaving properties in the values mapping will cause # a SQLAlchemy model error because SQLAlchemy expects the # properties attribute of an Image model to be a list and # not a dict. properties = values.pop('properties', {}) if image_id: image_ref = image_get(context, image_id, session=session) # Perform authorization check check_mutate_authorization(context, image_ref) -- def check_mutate_authorization(context, image_ref): if not context.is_image_mutable(image_ref): logger.info(_("Attempted to modify image user did not own.")) msg = _("You do not own this image") if image_ref.is_public: exc_class = exception.ForbiddenPublicImage else: exc_class = exception.Forbidden