def test_upload_data_to_store(self):
        req = unit_test_utils.get_fake_request()

        location = "file://foo/bar"
        size = 10
        checksum = "checksum"

        image_meta = {'id': unit_test_utils.UUID1,
                      'size': size}
        image_data = "blah"

        notifier = self.mox.CreateMockAnything()
        store = self.mox.CreateMockAnything()
        store.add(
            image_meta['id'],
            mox.IgnoreArg(),
            image_meta['size']).AndReturn((location, size, checksum, {}))

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        update_data = {'checksum': checksum,
                       'size': size}
        registry.update_image_metadata(req.context,
                                       image_meta['id'],
                                       update_data
                                       ).AndReturn(
                                           image_meta.update(update_data))
        self.mox.ReplayAll()

        actual_meta, actual_loc, loc_meta = upload_utils.upload_data_to_store(
            req, image_meta, image_data, store, notifier)

        self.mox.VerifyAll()

        self.assertEqual(actual_loc, location)
        self.assertEqual(actual_meta, image_meta.update(update_data))
    def test_upload_data_to_store_mismatch_checksum(self):
        req = unit_test_utils.get_fake_request()

        location = "file://foo/bar"
        size = 10
        checksum = "checksum"

        image_meta = {'id': unit_test_utils.UUID1,
                      'size': size}
        image_data = "blah"

        notifier = self.mox.CreateMockAnything()
        store = self.mox.CreateMockAnything()
        store.add(
            image_meta['id'],
            mox.IgnoreArg(),
            image_meta['size']).AndReturn((location,
                                           size,
                                           checksum + "NOT",
                                           {}))

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        update_data = {'checksum': checksum}
        registry.update_image_metadata(req.context,
                                       image_meta['id'],
                                       update_data).AndReturn(
                                               image_meta.update(update_data))
        notifier.error('image.upload', mox.IgnoreArg())
        self.mox.ReplayAll()

        self.assertRaises(webob.exc.HTTPBadRequest,
                          upload_utils.upload_data_to_store,
                          req, image_meta, image_data, store, notifier)

        self.mox.VerifyAll()
Exemple #3
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"] == "pending_delete":
            msg = _("Forbidden to delete a %s image.") % image["status"]
            LOG.debug(msg)
            raise HTTPForbidden(explanation=msg, request=req, content_type="text/plain")
        elif image["status"] == "deleted":
            msg = _("Image %s not found.") % id
            LOG.debug(msg)
            raise HTTPNotFound(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"]:
                upload_utils.initiate_deletion(req, image["location"], id, CONF.delayed_delete)
        except exception.NotFound as 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")
        except exception.Forbidden as e:
            msg = _("Forbidden to delete 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.delete", redact_loc(image))
            return Response(body="", status=200)
    def test_upload_data_to_store_not_found_after_upload(self):
        req = unit_test_utils.get_fake_request()

        location = "file://foo/bar"
        size = 10
        checksum = "checksum"

        image_meta = {'id': unit_test_utils.UUID1,
                      'size': size}
        image_data = "blah"

        notifier = self.mox.CreateMockAnything()
        store = self.mox.CreateMockAnything()
        store.add(
            image_meta['id'],
            mox.IgnoreArg(),
            image_meta['size']).AndReturn((location, size, checksum))

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        update_data = {'checksum': checksum,
                       'size': size}
        registry.update_image_metadata(req.context,
                                       image_meta['id'],
                                       update_data
                                       ).AndRaise(exception.NotFound)
        self.mox.StubOutWithMock(upload_utils, "safe_kill")
        upload_utils.safe_kill(req, image_meta['id'])
        self.mox.ReplayAll()

        self.assertRaises(webob.exc.HTTPPreconditionFailed,
                          upload_utils.upload_data_to_store,
                          req, image_meta, image_data, store, notifier)

        self.mox.VerifyAll()
Exemple #5
0
def create_image_cache(context, image_id):
    """Enqueue an image for caching if needed"""
    global g_job_queue, g_is_cache_raw_enabled
    if not g_is_cache_raw_enabled or not g_is_cache_raw_enabled.value:
        # Check & delete image cache only if RAW Caching is enabled
        # Note: UTs should pass without modifications
        return
    image_meta = registry.get_image_metadata(context, image_id)
    if not _is_caching_needed(image_meta):
        LOG.info(_LE("Caching not needed for:%s") % image_id)
        return

    # Schedule image for caching only if RAW Caching is enabled
    if not g_is_cache_raw_enabled.value:
        del image_meta['properties']['cache_raw']
        registry.update_image_metadata(context,
                                       image_id,
                                       image_meta,
                                       purge_props=True)
        return

    # Make sure we have all of the fields and that they are correctly set
    image_meta['properties']['cache_raw_status'] = 'Queued'
    image_meta['properties']['cache_raw_size'] = '-'
    if 'cache_raw_error' in image_meta['properties']:
        del image_meta['properties']['cache_raw_error']
    registry.update_image_metadata(context,
                                   image_id,
                                   image_meta,
                                   purge_props=True)

    LOG.info(_LI("Enqueuing image for conversion: %s") % image_id)
    g_job_queue.put(image_id)
    def test_upload_data_to_store(self):
        req = unit_test_utils.get_fake_request()

        location = "file://foo/bar"
        size = 10
        checksum = "checksum"

        image_meta = {'id': unit_test_utils.UUID1,
                      'size': size}
        image_data = "blah"

        notifier = self.mox.CreateMockAnything()
        store = self.mox.CreateMockAnything()
        store.add(
            image_meta['id'],
            mox.IgnoreArg(),
            image_meta['size']).AndReturn((location, size, checksum, {}))

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        update_data = {'checksum': checksum,
                       'size': size}
        registry.update_image_metadata(
            req.context, image_meta['id'],
            update_data).AndReturn(image_meta.update(update_data))
        self.mox.ReplayAll()

        actual_meta, location_data = upload_utils.upload_data_to_store(
            req, image_meta, image_data, store, notifier)

        self.mox.VerifyAll()

        self.assertEqual(location_data['url'], location)
        self.assertEqual(actual_meta, image_meta.update(update_data))
Exemple #7
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:
                upload_utils.safe_kill(req, image_meta['id'], 'queued')
                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:
                upload_utils.safe_kill(req, image_meta['id'], 'queued')
                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"), {
                  'image_id': image_id,
                  'scheme': scheme
              })

        self.notifier.info("image.prepare", redact_loc(image_meta))

        image_meta, location, loc_meta = upload_utils.upload_data_to_store(
            req, image_meta, image_data, store, self.notifier)

        self.notifier.info('image.upload', redact_loc(image_meta))

        return location, loc_meta
Exemple #8
0
    def test_upload_data_to_store_mismatch_checksum(self):
        req = unit_test_utils.get_fake_request()

        location = "file://foo/bar"
        size = 10
        checksum = "checksum"

        image_meta = {'id': unit_test_utils.UUID1,
                      'size': size}
        image_data = "blah"

        notifier = self.mox.CreateMockAnything()
        store = self.mox.CreateMockAnything()
        store.add(
            image_meta['id'],
            mox.IgnoreArg(),
            image_meta['size']).AndReturn((location, size, checksum + "NOT"))

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        update_data = {'checksum': checksum}
        registry.update_image_metadata(req.context,
                                       image_meta['id'],
                                       update_data).AndReturn(
                                               image_meta.update(update_data))

        self.mox.ReplayAll()

        self.assertRaises(webob.exc.HTTPBadRequest,
                          upload_utils.upload_data_to_store,
                          req, image_meta, image_data, store, notifier)

        self.mox.VerifyAll()
Exemple #9
0
    def test_upload_data_to_store_not_found_after_upload(self):
        req = unit_test_utils.get_fake_request()

        location = "file://foo/bar"
        size = 10
        checksum = "checksum"

        image_meta = {'id': unit_test_utils.UUID1, 'size': size}
        image_data = "blah"

        notifier = self.mox.CreateMockAnything()
        store = self.mox.CreateMockAnything()
        store.add(image_meta['id'], mox.IgnoreArg(),
                  image_meta['size']).AndReturn((location, size, checksum, {}))

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        update_data = {'checksum': checksum, 'size': size}
        registry.update_image_metadata(req.context, image_meta['id'],
                                       update_data).AndRaise(
                                           exception.NotFound)
        self.mox.StubOutWithMock(upload_utils, "initiate_deletion")
        upload_utils.initiate_deletion(req, location, image_meta['id'],
                                       mox.IsA(bool))
        self.mox.StubOutWithMock(upload_utils, "safe_kill")
        upload_utils.safe_kill(req, image_meta['id'])
        notifier.error('image.upload', mox.IgnoreArg())
        self.mox.ReplayAll()

        self.assertRaises(webob.exc.HTTPPreconditionFailed,
                          upload_utils.upload_data_to_store, req, image_meta,
                          image_data, store, notifier)

        self.mox.VerifyAll()
Exemple #10
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:
                upload_utils.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:
                upload_utils.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())

        self.notifier.info("image.prepare", redact_loc(image_meta))

        image_meta, location = upload_utils.upload_data_to_store(req,
                                                                 image_meta,
                                                                 image_data,
                                                                 store,
                                                                 self.notifier)

        self.notifier.info('image.upload', redact_loc(image_meta))

        return location
Exemple #11
0
def _kill(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'})
Exemple #12
0
def _kill(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"})
Exemple #13
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
        """

        scheme = req.headers.get("x-image-meta-store", CONF.glance_store.default_store)

        store = self.get_store_or_400(req, scheme)

        copy_from = self._copy_from(req)
        if copy_from:
            try:
                image_data, image_size = self._get_from_store(req.context, copy_from, dest=store)
            except Exception:
                upload_utils.safe_kill(req, image_meta["id"], "queued")
                msg = _LE("Copy from external source '%(scheme)s' failed for " "image: %(image)s") % {
                    "scheme": scheme,
                    "image": image_meta["id"],
                }
                LOG.exception(msg)
                return
            image_meta["size"] = image_size or image_meta["size"]
        else:
            try:
                req.get_content_type(("application/octet-stream",))
            except exception.InvalidContentType:
                upload_utils.safe_kill(req, image_meta["id"], "queued")
                msg = "Content-Type must be application/octet-stream"
                LOG.debug(msg)
                raise HTTPBadRequest(explanation=msg)

            image_data = req.body_file

        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",
            {"image_id": image_id, "scheme": scheme},
        )

        self.notifier.info("image.prepare", redact_loc(image_meta))

        image_meta, location_data = upload_utils.upload_data_to_store(req, image_meta, image_data, store, self.notifier)

        self.notifier.info("image.upload", redact_loc(image_meta))

        return location_data
Exemple #14
0
def _safe_update_image_metadata(context, image_id, image_meta):
    try:
        registry.update_image_metadata(context, image_id, image_meta)
    except Exception as e:
        err = encodeutils.exception_to_unicode(e)
        LOG.error(
            _LE("Error updating image metadata: "
                "%(img)s, err: %(err)s") % {
                    'img': image_meta['id'],
                    'err': err
                })
    def test_safe_kill(self):
        req = unit_test_utils.get_fake_request()
        id = unit_test_utils.UUID1

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        registry.update_image_metadata(req.context, id, {'status': 'killed'})
        self.mox.ReplayAll()

        upload_utils.safe_kill(req, id)

        self.mox.VerifyAll()
Exemple #16
0
    def test_safe_kill(self):
        req = unit_test_utils.get_fake_request()
        id = unit_test_utils.UUID1

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        registry.update_image_metadata(req.context, id, {'status': 'killed'})
        self.mox.ReplayAll()

        upload_utils.safe_kill(req, id)

        self.mox.VerifyAll()
Exemple #17
0
def _kill(req, image_id, from_state):
    """
    Marks the image status to `killed`.

    :param req: The WSGI/Webob Request object
    :param image_id: Opaque image identifier
    :param from_state: Permitted current status for transition to 'killed'
    """
    # TODO(dosaboy): http://docs.openstack.org/developer/glance/statuses.html
    # needs updating to reflect the fact that queued->killed and saving->killed
    # are both allowed.
    registry.update_image_metadata(
        req.context, image_id, {'status': 'killed'}, from_state=from_state)
Exemple #18
0
    def test_safe_kill_with_error(self):
        req = unit_test_utils.get_fake_request()
        id = unit_test_utils.UUID1

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        registry.update_image_metadata(req.context, id, {
            'status': 'killed'
        }, 'saving').AndRaise(Exception())
        self.mox.ReplayAll()

        upload_utils.safe_kill(req, id, 'saving')

        self.mox.VerifyAll()
Exemple #19
0
def _kill(req, image_id, from_state):
    """
    Marks the image status to `killed`.

    :param req: The WSGI/Webob Request object
    :param image_id: Opaque image identifier
    :param from_state: Permitted current status for transition to 'killed'
    """
    # TODO(dosaboy): http://docs.openstack.org/developer/glance/statuses.html
    # needs updating to reflect the fact that queued->killed and saving->killed
    # are both allowed.
    registry.update_image_metadata(req.context, image_id,
                                   {'status': 'killed'},
                                   from_state=from_state)
Exemple #20
0
def _cache_img_to_raw(context, image_id):
    """Cache an image to RAW"""

    # Start caching
    LOG.info(_LI("Caching image %s") % image_id)
    image_meta = registry.get_image_metadata(context, image_id)
    image_meta_to_update = {'properties': {'cache_raw_status': 'Caching'}}
    registry.update_image_metadata(context, image_id, image_meta_to_update)

    # Set the paths
    base = CONF.cache_raw_conversion_dir + "/" + image_id
    orig_file = base + "_orig"
    converted_file = base + "_raw"
    converted_image = image_id + "_raw"
    # Get cluster fsid
    ceph_cfg_file = CONF.glance_store.rbd_store_ceph_conf
    with rados.Rados(conffile=ceph_cfg_file) as cluster:
        fsid = cluster.get_fsid()
    dest_url = "rbd://%s/%s/%s/%s" % (fsid, CONF.glance_store.rbd_store_pool,
                                      converted_image, "snap")

    # Do the conversion
    _fetch_to_file(image_id, image_meta, orig_file)
    try:
        _convert_to_volume_format(orig_file, converted_file, 'raw', image_id)
    except exception.ConvertToSameFormat as ex:
        raise exception.ImageUncacheable(
            image_id=image_id,
            reason=_("The format of the image is (%(fmt)s) "
                     "not (%(orig)s), please specify the correct format "
                     "when creating the image") % {
                         'fmt': ex.fmt,
                         'orig': image_meta.get('disk_format')
                     })

    with reserve_space(image_id, _get_sparse_file_size(converted_file),
                       CONF.glance_store.rbd_store_pool):
        _import_from_file(converted_file, dest_url, image_id)

    # Cleanup
    os.unlink(orig_file)
    os.unlink(converted_file)

    # Finalize caching
    image_size = _get_rbd_image_size(dest_url, image_id)
    image_meta_to_update['properties']['cache_raw_status'] = 'Cached'
    image_meta_to_update['properties']['cache_raw_url'] = dest_url
    image_meta_to_update['properties']['cache_raw_size'] = image_size
    registry.update_image_metadata(context, image_id, image_meta_to_update)
    LOG.info(_LI("Caching completed for image: %s") % image_id)
    def test_safe_kill_with_error(self):
        req = unit_test_utils.get_fake_request()
        id = unit_test_utils.UUID1

        self.mox.StubOutWithMock(registry, "update_image_metadata")
        registry.update_image_metadata(req.context,
                                       id,
                                       {'status': 'killed'},
                                       'saving'
                                       ).AndRaise(Exception())
        self.mox.ReplayAll()

        upload_utils.safe_kill(req, id, 'saving')

        self.mox.VerifyAll()
Exemple #22
0
    def _activate(self, req, image_id, location, location_metadata=None):
        """
        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
        :param location_metadata: a dictionary of storage specfic information
        """
        image_meta = {}
        image_meta['location'] = location
        image_meta['status'] = 'active'
        if location_metadata:
            image_meta['location_data'] = [{'url': location,
                                            'metadata': location_metadata}]

        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") % {'e': e}
            LOG.debug(msg)
            raise HTTPBadRequest(explanation=msg,
                                 request=req,
                                 content_type="text/plain")
Exemple #23
0
    def _activate(self, req, image_id, location, location_metadata=None, from_state=None):
        """
        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
        :param location_metadata: a dictionary of storage specific information
        """
        image_meta = {}
        image_meta["location"] = location
        image_meta["status"] = "active"
        if location_metadata:
            image_meta["location_data"] = [{"url": location, "metadata": location_metadata}]

        try:
            s = from_state
            image_meta_data = registry.update_image_metadata(req.context, image_id, image_meta, from_state=s)
            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.Duplicate:
            with excutils.save_and_reraise_exception():
                # Delete image data since it has been supersceded by another
                # upload and re-raise.
                LOG.debug(
                    "duplicate operation - deleting image data for "
                    " %(id)s (location:%(location)s)" % {"id": image_id, "location": image_meta["location"]}
                )
                upload_utils.initiate_deletion(req, image_meta["location"], image_id, CONF.delayed_delete)
        except exception.Invalid as e:
            msg = "Failed to activate image. Got error: %s" % utils.exception_to_str(e)
            LOG.debug(msg)
            raise HTTPBadRequest(explanation=msg, request=req, content_type="text/plain")
Exemple #24
0
    def _activate(self,
                  req,
                  image_id,
                  location,
                  location_metadata=None,
                  from_state=None):
        """
        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
        :param location_metadata: a dictionary of storage specific information
        """
        image_meta = {}
        image_meta['location'] = location
        image_meta['status'] = 'active'
        if location_metadata:
            image_meta['location_data'] = [{
                'url': location,
                'metadata': location_metadata
            }]

        try:
            s = from_state
            image_meta_data = registry.update_image_metadata(req.context,
                                                             image_id,
                                                             image_meta,
                                                             from_state=s)
            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.Duplicate:
            # Delete image data since it has been supersceded by another
            # upload.
            LOG.debug(
                _("duplicate operation - deleting image data for %(id)s "
                  "(location:%(location)s)") % {
                      'id': image_id,
                      'location': image_meta['location']
                  })
            upload_utils.initiate_deletion(req, image_meta['location'],
                                           image_id, CONF.delayed_delete)
            # Then propagate the exception.
            raise
        except exception.Invalid as e:
            msg = _("Failed to activate image. Got error: %(e)s") % {'e': e}
            LOG.debug(msg)
            raise HTTPBadRequest(explanation=msg,
                                 request=req,
                                 content_type="text/plain")
Exemple #25
0
    def _activate(self, req, image_id, location, location_metadata=None,
                  from_state=None):
        """
        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
        :param location_metadata: a dictionary of storage specific information
        """
        image_meta = {}
        image_meta['location'] = location
        image_meta['status'] = 'active'
        if location_metadata:
            image_meta['location_data'] = [{'url': location,
                                            'metadata': location_metadata}]

        try:
            s = from_state
            image_meta_data = registry.update_image_metadata(req.context,
                                                             image_id,
                                                             image_meta,
                                                             from_state=s)
            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.Duplicate:
            # Delete image data since it has been supersceded by another
            # upload.
            LOG.debug("duplicate operation - deleting image data for %s "
                      "(location:%s)" %
                      (image_id, image_meta['location']))
            upload_utils.initiate_deletion(req, image_meta['location'],
                                           image_id, CONF.delayed_delete)
            # Then propagate the exception.
            raise
        except exception.Invalid as e:
            msg = _("Failed to activate image. Got error: %(e)s") % {'e': e}
            LOG.debug(msg)
            raise HTTPBadRequest(explanation=msg,
                                 request=req,
                                 content_type="text/plain")
Exemple #26
0
    def _activate(self, req, image_id, location_data, from_state=None):
        """
        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_data: Location of where Glance stored this image
        """
        image_meta = {}
        image_meta['location'] = location_data['url']
        image_meta['status'] = 'active'
        image_meta['location_data'] = [location_data]

        try:
            s = from_state
            image_meta_data = registry.update_image_metadata(req.context,
                                                             image_id,
                                                             image_meta,
                                                             from_state=s)
            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.Duplicate:
            with excutils.save_and_reraise_exception():
                # Delete image data since it has been supersceded by another
                # upload and re-raise.
                LOG.debug("duplicate operation - deleting image data for "
                          " %(id)s (location:%(location)s)" %
                          {'id': image_id, 'location': image_meta['location']})
                upload_utils.initiate_deletion(req, image_meta['location'],
                                               image_id, CONF.delayed_delete)
        except exception.Invalid as e:
            msg = ("Failed to activate image. Got error: %s" %
                   utils.exception_to_str(e))
            LOG.debug(msg)
            raise HTTPBadRequest(explanation=msg,
                                 request=req,
                                 content_type="text/plain")
Exemple #27
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'] == 'pending_delete':
            msg = (_("Forbidden to delete a %s image.") % image['status'])
            LOG.debug(msg)
            raise HTTPForbidden(explanation=msg, request=req,
                                content_type="text/plain")
        elif image['status'] == 'deleted':
            msg = _("Image %s not found.") % id
            LOG.debug(msg)
            raise HTTPNotFound(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
            image = 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']:
                upload_utils.initiate_deletion(req, image['location'], id,
                                               CONF.delayed_delete)
        except exception.NotFound as e:
            msg = _("Failed to find image to delete: %(e)s") % {'e': e}
            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 delete image: %(e)s") % {'e': e}
            for line in msg.split('\n'):
                LOG.info(line)
            raise HTTPForbidden(explanation=msg,
                                request=req,
                                content_type="text/plain")
        else:
            self.notifier.info('image.delete', redact_loc(image))
            return Response(body='', status=200)
Exemple #28
0
def upload_data_to_store(req, image_meta, image_data, store, notifier):
    """
    Upload image data to specified store.

    Upload image data to the store and cleans up on error.
    """
    image_id = image_meta['id']

    db_api = glance.db.get_api()
    image_size = image_meta.get('size', None)

    try:
        remaining = glance.api.common.check_quota(
            req.context, image_size, db_api, image_id=image_id)
        if remaining is not None:
            image_data = utils.LimitingReader(image_data, remaining)

        (location,
         size,
         checksum,
         locations_metadata) = glance.store.store_add_to_backend(
             image_meta['id'],
             utils.CooperativeReader(image_data),
             image_meta['size'],
             store)

        try:
            # recheck the quota in case there were simultaneous uploads that
            # did not provide the size
            glance.api.common.check_quota(
                req.context, size, db_api, image_id=image_id)
        except exception.StorageQuotaFull:
            LOG.info(_('Cleaning up %s after exceeding the quota') % image_id)
            glance.store.safe_delete_from_backend(
                location, req.context, image_meta['id'])
            raise

        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'.") % {'attr': attr,
                                                   'supplied': supplied,
                                                   'actual': actual})
                LOG.error(msg)
                safe_kill(req, image_id)
                initiate_deletion(req, location, image_id, CONF.delayed_delete)
                raise webob.exc.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"), {'image_id': image_id,
                                   'checksum': checksum,
                                   'size': size})
        update_data = {'checksum': checksum,
                       'size': size}
        try:
            image_meta = registry.update_image_metadata(req.context,
                                                        image_id,
                                                        update_data)

        except exception.NotFound as e:
            msg = _("Image %s could not be found after upload. The image may "
                    "have been deleted during the upload.") % image_id
            LOG.info(msg)

            # NOTE(jculp): we need to clean up the datastore if an image
            # resource is deleted while the image data is being uploaded
            #
            # We get "location" from above call to store.add(), any
            # exceptions that occur there handle this same issue internally,
            # Since this is store-agnostic, should apply to all stores.
            initiate_deletion(req, location, image_id, CONF.delayed_delete)
            raise webob.exc.HTTPPreconditionFailed(explanation=msg,
                                                   request=req,
                                                   content_type='text/plain')

    except exception.Duplicate as e:
        msg = _("Attempt to upload duplicate image: %s") % e
        LOG.debug(msg)
        # NOTE(dosaboy): do not delete the image since it is likely that this
        # conflict is a result of another concurrent upload that will be
        # successful.
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPConflict(explanation=msg,
                                     request=req,
                                     content_type="text/plain")

    except exception.Forbidden as e:
        msg = _("Forbidden upload attempt: %s") % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except exception.StorageQuotaFull as e:
        msg = (_("Denying attempt to upload image because it exceeds the ."
                 "quota: %s") % e)
        LOG.info(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except webob.exc.HTTPError:
        #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.
        msg = _("Received HTTP error while uploading image %s") % image_id
        notifier.error('image.upload', msg)
        with excutils.save_and_reraise_exception():
            LOG.exception(msg)
            safe_kill(req, image_id)

    except (ValueError, IOError) as e:
        msg = _("Client disconnected before sending all data to backend")
        LOG.debug(msg)
        safe_kill(req, image_id)
        raise webob.exc.HTTPBadRequest(explanation=msg,
                                       content_type="text/plain",
                                       request=req)

    except Exception as e:
        msg = _("Failed to upload image %s") % image_id
        LOG.exception(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPInternalServerError(explanation=msg,
                                                request=req,
                                                content_type='text/plain')

    return image_meta, location, locations_metadata
Exemple #29
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'] == 'pending_delete':
            msg = "Forbidden to delete a %s image." % image['status']
            LOG.debug(msg)
            raise HTTPForbidden(explanation=msg, request=req,
                                content_type="text/plain")
        elif image['status'] == 'deleted':
            msg = "Image %s not found." % id
            LOG.debug(msg)
            raise HTTPNotFound(explanation=msg, request=req,
                               content_type="text/plain")

        if image['location'] and CONF.delayed_delete:
            status = 'pending_delete'
        else:
            status = 'deleted'

        ori_status = image['status']

        try:
            # Update the image from the registry first, since we rely on it
            # for authorization checks.
            # See https://bugs.launchpad.net/glance/+bug/1065187
            image = registry.update_image_metadata(req.context, id,
                                                   {'status': status})

            try:
                # 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']:
                    for loc_data in image['location_data']:
                        if loc_data['status'] == 'active':
                            upload_utils.initiate_deletion(req, loc_data, id)
            except Exception:
                with excutils.save_and_reraise_exception():
                    registry.update_image_metadata(req.context, id,
                                                   {'status': ori_status})
            registry.delete_image_metadata(req.context, id)
        except exception.NotFound as e:
            msg = (_("Failed to find image to delete: %s") %
                   utils.exception_to_str(e))
            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 delete image: %s") %
                   utils.exception_to_str(e))
            for line in msg.split('\n'):
                LOG.info(line)
            raise HTTPForbidden(explanation=msg,
                                request=req,
                                content_type="text/plain")
        else:
            self.notifier.info('image.delete', redact_loc(image))
            return Response(body='', status=200)
Exemple #30
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")

        if req.context.is_admin is False:
            # Once an image is 'active' only an admin can
            # modify certain core metadata keys
            for key in ACTIVE_IMMUTABLE:
                if (orig_status == 'active' and image_meta.get(key) is not None
                    and image_meta.get(key) != orig_image_meta.get(key)):
                    msg = _("Forbidden to modify '%s' of active image.") % key
                    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 = (strutils.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")

        # ensure requester has permissions to create/update/delete properties
        # according to property-protections.conf
        orig_keys = set(orig_image_meta['properties'])
        new_keys = set(image_meta['properties'])
        self._enforce_update_protected_props(
                orig_keys.intersection(new_keys), image_meta,
                orig_image_meta, req)
        self._enforce_create_protected_props(
                new_keys.difference(orig_keys), req)
        if purge_props:
            self._enforce_delete_protected_props(
                    orig_keys.difference(new_keys), image_meta,
                    orig_image_meta, req)

        self._enforce_image_property_quota(image_meta,
                                           orig_image_meta=orig_image_meta,
                                           purge_props=purge_props,
                                           req=req)

        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") %
                   {'e': e})
            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") % {'e': e}
            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") % {'e': e}
            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 = redact_loc(image_meta)

        self._enforce_read_protected_props(image_meta, req)

        return {'image_meta': image_meta}
Exemple #31
0
def upload_data_to_store(req, image_meta, image_data, store, notifier):
    """
    Upload image data to specified store.

    Upload image data to the store and cleans up on error.
    """
    image_id = image_meta['id']
    try:
        (location, size, checksum,
         locations_metadata) = glance.store.store_add_to_backend(
             image_meta['id'], utils.CooperativeReader(image_data),
             image_meta['size'], store)

        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)
                safe_kill(req, image_id)
                initiate_deletion(req, location, image_id, CONF.delayed_delete)
                raise webob.exc.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}
        try:
            image_meta = registry.update_image_metadata(
                req.context, image_id, update_data)

        except exception.NotFound as e:
            msg = _("Image %s could not be found after upload. The image may "
                    "have been deleted during the upload.") % image_id
            LOG.info(msg)

            # NOTE(jculp): we need to clean up the datastore if an image
            # resource is deleted while the image data is being uploaded
            #
            # We get "location" from above call to store.add(), any
            # exceptions that occur there handle this same issue internally,
            # Since this is store-agnostic, should apply to all stores.
            initiate_deletion(req, location, image_id, CONF.delayed_delete)
            raise webob.exc.HTTPPreconditionFailed(explanation=msg,
                                                   request=req,
                                                   content_type='text/plain')

    except exception.Duplicate as e:
        msg = _("Attempt to upload duplicate image: %s") % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPConflict(explanation=msg,
                                     request=req,
                                     content_type="text/plain")

    except exception.Forbidden as e:
        msg = _("Forbidden upload attempt: %s") % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except webob.exc.HTTPError:
        #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.
        msg = _("Received HTTP error while uploading image %s") % image_id
        notifier.error('image.upload', msg)
        with excutils.save_and_reraise_exception():
            LOG.exception(msg)
            safe_kill(req, image_id)

    except (ValueError, IOError) as e:
        msg = _("Client disconnected before sending all data to backend")
        LOG.debug(msg)
        safe_kill(req, image_id)
        raise webob.exc.HTTPBadRequest(explanation=msg,
                                       content_type="text/plain",
                                       request=req)

    except Exception as e:
        msg = _("Failed to upload image %s") % image_id
        LOG.exception(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPInternalServerError(explanation=msg,
                                                request=req,
                                                content_type='text/plain')

    return image_meta, location, locations_metadata
Exemple #32
0
def upload_data_to_store(req, image_meta, image_data, store, notifier):
    """
    Upload image data to specified store.

    Upload image data to the store and cleans up on error.
    """
    image_id = image_meta['id']

    db_api = glance.db.get_api()
    image_size = image_meta.get('size')

    try:
        remaining = glance.api.common.check_quota(req.context,
                                                  image_size,
                                                  db_api,
                                                  image_id=image_id)
        if remaining is not None:
            image_data = utils.LimitingReader(image_data, remaining)

        (uri, size, checksum,
         location_metadata) = store_api.store_add_to_backend(
             image_meta['id'], utils.CooperativeReader(image_data),
             image_meta['size'], store)

        location_data = {
            'url': uri,
            'metadata': location_metadata,
            'status': 'active'
        }

        try:
            # recheck the quota in case there were simultaneous uploads that
            # did not provide the size
            glance.api.common.check_quota(req.context,
                                          size,
                                          db_api,
                                          image_id=image_id)
        except exception.StorageQuotaFull:
            with excutils.save_and_reraise_exception():
                LOG.info(
                    _('Cleaning up %s after exceeding '
                      'the quota') % image_id)
                store_utils.safe_delete_from_backend(req.context,
                                                     image_meta['id'],
                                                     location_data)

        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'.") % {
                             'attr': attr,
                             'supplied': supplied,
                             'actual': actual
                         })
                LOG.error(msg)
                safe_kill(req, image_id)
                initiate_deletion(req, location_data, image_id)
                raise webob.exc.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", {
                'image_id': image_id,
                'checksum': checksum,
                'size': size
            })
        update_data = {'checksum': checksum, 'size': size}
        try:
            image_meta = registry.update_image_metadata(
                req.context, image_id, update_data)

        except exception.NotFound as e:
            msg = _("Image %s could not be found after upload. The image may "
                    "have been deleted during the upload.") % image_id
            LOG.info(msg)

            # NOTE(jculp): we need to clean up the datastore if an image
            # resource is deleted while the image data is being uploaded
            #
            # We get "location_data" from above call to store.add(), any
            # exceptions that occur there handle this same issue internally,
            # Since this is store-agnostic, should apply to all stores.
            initiate_deletion(req, location_data, image_id)
            raise webob.exc.HTTPPreconditionFailed(explanation=msg,
                                                   request=req,
                                                   content_type='text/plain')

    except exception.Duplicate as e:
        msg = u"Attempt to upload duplicate image: %s" % e
        LOG.debug(msg)
        # NOTE(dosaboy): do not delete the image since it is likely that this
        # conflict is a result of another concurrent upload that will be
        # successful.
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPConflict(explanation=msg,
                                     request=req,
                                     content_type="text/plain")

    except exception.Forbidden as e:
        msg = u"Forbidden upload attempt: %s" % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPForbidden(explanation=msg,
                                      request=req,
                                      content_type="text/plain")

    except exception.StorageFull as e:
        msg = _("Image storage media is full: %s") % utils.exception_to_str(e)
        LOG.error(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except exception.StorageWriteDenied as e:
        msg = (_("Insufficient permissions on image storage media: %s") %
               utils.exception_to_str(e))
        LOG.error(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except exception.StorageQuotaFull as e:
        msg = (_("Denying attempt to upload image because it exceeds the ."
                 "quota: %s") % utils.exception_to_str(e))
        LOG.info(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except webob.exc.HTTPError:
        #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.
        msg = _("Received HTTP error while uploading image %s") % image_id
        notifier.error('image.upload', msg)
        with excutils.save_and_reraise_exception():
            LOG.exception(msg)
            safe_kill(req, image_id)

    except (ValueError, IOError) as e:
        msg = "Client disconnected before sending all data to backend"
        LOG.debug(msg)
        safe_kill(req, image_id)
        raise webob.exc.HTTPBadRequest(explanation=msg,
                                       content_type="text/plain",
                                       request=req)

    except Exception as e:
        msg = _("Failed to upload image %s") % image_id
        LOG.exception(msg)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.HTTPInternalServerError(explanation=msg,
                                                request=req,
                                                content_type='text/plain')

    return image_meta, location_data
Exemple #33
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)
Exemple #34
0
def upload_data_to_store(req, image_meta, image_data, store, notifier):
    """
    Upload image data to specified store.

    Upload image data to the store and cleans up on error.
    """
    image_id = image_meta['id']
    try:
        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)
                safe_kill(req, image_id)
                initiate_deletion(req, location, image_id, CONF.delayed_delete)
                raise webob.exc.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)

    except exception.Duplicate as e:
        msg = _("Attempt to upload duplicate image: %s") % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        raise webob.exc.HTTPConflict(explanation=msg, request=req)

    except exception.Forbidden as e:
        msg = _("Forbidden upload attempt: %s") % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except webob.exc.HTTPError as e:
        LOG.exception(_("Received HTTP error while uploading image."))
        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"))
        safe_kill(req, image_id)
        raise webob.exc.HTTPInternalServerError(request=req)

    return image_meta, location
Exemple #35
0
def upload_data_to_store(req, image_meta, image_data, store, notifier):
    """
    Upload image data to specified store.

    Upload image data to the store and cleans up on error.
    """
    image_id = image_meta['id']
    try:
        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)
                safe_kill(req, image_id)
                initiate_deletion(req, location, image_id, CONF.delayed_delete)
                raise webob.exc.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}
        try:
            image_meta = registry.update_image_metadata(req.context,
                                                        image_id,
                                                        update_data)

        except exception.NotFound as e:
            msg = _("Image %s could not be found after upload. The image may "
                    "have been deleted during the upload.") % image_id
            LOG.info(msg)
            raise webob.exc.HTTPPreconditionFailed(explanation=msg,
                                                   request=req,
                                                   content_type='text/plain')

    except exception.Duplicate as e:
        msg = _("Attempt to upload duplicate image: %s") % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        raise webob.exc.HTTPConflict(explanation=msg,
                                     request=req,
                                     content_type="text/plain")

    except exception.Forbidden as e:
        msg = _("Forbidden upload attempt: %s") % e
        LOG.debug(msg)
        safe_kill(req, image_id)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        notifier.error('image.upload', msg)
        raise webob.exc.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)
        safe_kill(req, image_id)
        raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                  request=req,
                                                  content_type='text/plain')

    except webob.exc.HTTPError as e:
        LOG.exception(_("Received HTTP error while uploading image."))
        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:
        msg = _("Failed to upload image")
        LOG.exception(msg)
        safe_kill(req, image_id)
        raise webob.exc.HTTPInternalServerError(explanation=msg,
                                                request=req,
                                                content_type='text/plain')

    return image_meta, location