Ejemplo n.º 1
0
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'})
Ejemplo n.º 2
0
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"})
Ejemplo n.º 3
0
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'})
Ejemplo n.º 4
0
    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"})
Ejemplo n.º 5
0
    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'})
Ejemplo n.º 6
0
    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")
Ejemplo n.º 7
0
    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")
Ejemplo n.º 8
0
    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")
Ejemplo n.º 9
0
    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")
Ejemplo n.º 10
0
    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")
Ejemplo n.º 11
0
    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")
Ejemplo n.º 12
0
    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")
Ejemplo n.º 13
0
    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")
Ejemplo n.º 14
0
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'})
Ejemplo n.º 15
0
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'})
Ejemplo n.º 16
0
    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")
Ejemplo n.º 17
0
    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")
Ejemplo n.º 18
0
    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)
Ejemplo n.º 19
0
    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)
Ejemplo n.º 20
0
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'})
Ejemplo n.º 21
0
    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")
Ejemplo n.º 22
0
    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}
Ejemplo n.º 23
0
    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)
Ejemplo n.º 24
0
    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)
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
0
    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}
Ejemplo n.º 27
0
    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)
Ejemplo n.º 28
0
    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)
Ejemplo n.º 29
0
    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)
Ejemplo n.º 30
0
    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)
Ejemplo n.º 31
0
    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)
Ejemplo n.º 32
0
    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)