Пример #1
0
    def update_migrated_volume(self, ctxt, volume, new_volume,
                               original_volume_status):
        """Return model update from RBD for migrated volume.

        This method should rename the back-end volume name(id) on the
        destination host back to its original name(id) on the source host.

        :param ctxt: The context used to run the method update_migrated_volume
        :param volume: The original volume that was migrated to this backend
        :param new_volume: The migration volume object that was created on
                           this backend as part of the migration process
        :param original_volume_status: The status of the original volume
        :returns: model_update to update DB with any needed changes
        """
        name_id = None
        provider_location = None

        existing_name = CONF.volume_name_template % new_volume.id
        wanted_name = CONF.volume_name_template % volume.id
        with RADOSClient(self) as client:
            try:
                self.RBDProxy().rename(client.ioctx,
                                       utils.convert_str(existing_name),
                                       utils.convert_str(wanted_name))
            except self.rbd.ImageNotFound:
                LOG.error(_LE('Unable to rename the logical volume '
                              'for volume %s.'), volume.id)
                # If the rename fails, _name_id should be set to the new
                # volume id and provider_location should be set to the
                # one from the new volume as well.
                name_id = new_volume._name_id or new_volume.id
                provider_location = new_volume['provider_location']
        return {'_name_id': name_id, 'provider_location': provider_location}
Пример #2
0
    def _check_restore_vol_size(self, backup_base, restore_vol, restore_length,
                                src_pool):
        """Ensure that the restore volume is the correct size.

        If the restore volume was bigger than the backup, the diff restore will
        shrink it to the size of the original backup so we need to
        post-process and resize it back to its expected size.
        """
        with rbd_driver.RADOSClient(self, self._ceph_backup_pool) as client:
            adjust_size = 0
            base_image = self.rbd.Image(client.ioctx,
                                        utils.convert_str(backup_base),
                                        read_only=True)
            try:
                if restore_length != base_image.size():
                    adjust_size = restore_length
            finally:
                base_image.close()

        if adjust_size:
            with rbd_driver.RADOSClient(self, src_pool) as client:
                restore_vol_encode = utils.convert_str(restore_vol)
                dest_image = self.rbd.Image(client.ioctx, restore_vol_encode)
                try:
                    LOG.debug("Adjusting restore vol size")
                    dest_image.resize(adjust_size)
                finally:
                    dest_image.close()
Пример #3
0
    def delete_snapshot(self, snapshot):
        """Deletes an rbd snapshot."""
        # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are
        #                utf-8 otherwise librbd will barf.
        volume_name = utils.convert_str(snapshot.volume_name)
        snap_name = utils.convert_str(snapshot.name)

        with RBDVolumeProxy(self, volume_name) as volume:
            try:
                volume.unprotect_snap(snap_name)
            except self.rbd.InvalidArgument:
                LOG.info(
                    _LI("InvalidArgument: Unable to unprotect snapshot %s."),
                    snap_name)
            except self.rbd.ImageNotFound:
                LOG.info(
                    _LI("ImageNotFound: Unable to unprotect snapshot %s."),
                    snap_name)
            except self.rbd.ImageBusy:
                children_list = self._get_children_info(volume, snap_name)

                if children_list:
                    for (pool, image) in children_list:
                        LOG.info(_LI('Image %(pool)s/%(image)s is dependent '
                                     'on the snapshot %(snap)s.'),
                                 {'pool': pool,
                                  'image': image,
                                  'snap': snap_name})

                raise exception.SnapshotIsBusy(snapshot_name=snap_name)
            try:
                volume.remove_snap(snap_name)
            except self.rbd.ImageNotFound:
                LOG.info(_LI("Snapshot %s does not exist in backend."),
                         snap_name)
Пример #4
0
 def delete_snapshot(self, snapshot):
     """Deletes an rbd snapshot."""
     # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are
     #                utf-8 otherwise librbd will barf.
     volume_name = utils.convert_str(snapshot['volume_name'])
     snap_name = utils.convert_str(snapshot['name'])
     with RBDVolumeProxy(self, volume_name) as volume:
         try:
             volume.unprotect_snap(snap_name)
         except self.rbd.ImageBusy:
             raise exception.SnapshotIsBusy(snapshot_name=snap_name)
         volume.remove_snap(snap_name)
Пример #5
0
 def _clone(self, volume, src_pool, src_image, src_snap):
     LOG.debug('cloning %(pool)s/%(img)s@%(snap)s to %(dst)s',
               dict(pool=src_pool, img=src_image, snap=src_snap,
                    dst=volume['name']))
     with RADOSClient(self, src_pool) as src_client:
         with RADOSClient(self) as dest_client:
             self.RBDProxy().clone(src_client.ioctx,
                                   utils.convert_str(src_image),
                                   utils.convert_str(src_snap),
                                   dest_client.ioctx,
                                   utils.convert_str(volume['name']),
                                   features=src_client.features)
Пример #6
0
    def _get_backup_base_name(self, volume_id, backup_id=None, diff_format=False):
        """Return name of base image used for backup.

        Incremental backups use a new base name so we support old and new style
        format.
        """
        # Ensure no unicode
        if diff_format:
            return utils.convert_str("volume-%s.backup.base" % volume_id)
        else:
            if backup_id is None:
                msg = _("Backup id required")
                raise exception.InvalidParameterValue(msg)
            return utils.convert_str("volume-%s.backup.%s" % (volume_id, backup_id))
Пример #7
0
    def __init__(self, driver, name, pool=None, snapshot=None, read_only=False):
        client, ioctx = driver._connect_to_rados(pool)
        if snapshot is not None:
            snapshot = utils.convert_str(snapshot)

        try:
            self.volume = driver.rbd.Image(ioctx, utils.convert_str(name), snapshot=snapshot, read_only=read_only)
        except driver.rbd.Error:
            LOG.exception(_LE("error opening rbd image %s"), name)
            driver._disconnect_from_rados(client, ioctx)
            raise
        self.driver = driver
        self.client = client
        self.ioctx = ioctx
Пример #8
0
    def _rbd_diff_transfer(
        self,
        src_name,
        src_pool,
        dest_name,
        dest_pool,
        src_user,
        src_conf,
        dest_user,
        dest_conf,
        src_snap=None,
        from_snap=None,
    ):
        """Copy only extents changed between two points.

        If no snapshot is provided, the diff extents will be all those changed
        since the rbd volume/base was created, otherwise it will be those
        changed since the snapshot was created.
        """
        LOG.debug(
            "Performing differential transfer from '%(src)s' to " "'%(dest)s'", {"src": src_name, "dest": dest_name}
        )

        # NOTE(dosaboy): Need to be tolerant of clusters/clients that do
        # not support these operations since at the time of writing they
        # were very new.

        src_ceph_args = self._ceph_args(src_user, src_conf, pool=src_pool)
        dest_ceph_args = self._ceph_args(dest_user, dest_conf, pool=dest_pool)

        cmd1 = ["rbd", "export-diff"] + src_ceph_args
        if from_snap is not None:
            cmd1.extend(["--from-snap", from_snap])
        if src_snap:
            path = utils.convert_str("%s/%s@%s" % (src_pool, src_name, src_snap))
        else:
            path = utils.convert_str("%s/%s" % (src_pool, src_name))
        cmd1.extend([path, "-"])

        cmd2 = ["rbd", "import-diff"] + dest_ceph_args
        rbd_path = utils.convert_str("%s/%s" % (dest_pool, dest_name))
        cmd2.extend(["-", rbd_path])

        ret, stderr = self._piped_execute(cmd1, cmd2)
        if ret:
            msg = _("RBD diff op failed - (ret=%(ret)s stderr=%(stderr)s)") % {"ret": ret, "stderr": stderr}
            LOG.info(msg)
            raise exception.BackupRBDOperationFailed(msg)
Пример #9
0
    def manage_existing(self, volume, existing_ref):
        """Manages an existing image.

        Renames the image name to match the expected name for the volume.
        Error checking done by manage_existing_get_size is not repeated.

        :param volume:
            volume ref info to be set
        :param existing_ref:
            existing_ref is a dictionary of the form:
            {'source-name': <name of rbd image>}
        """
        # Raise an exception if we didn't find a suitable rbd image.
        with RADOSClient(self) as client:
            rbd_name = existing_ref["source-name"]
            self.RBDProxy().rename(client.ioctx, utils.convert_str(rbd_name), utils.convert_str(volume["name"]))
Пример #10
0
    def _connect_to_rados(self, pool=None):
        LOG.debug("opening connection to ceph cluster (timeout=%s).",
                  self.configuration.rados_connect_timeout)

        client = self.rados.Rados(
            rados_id=self.configuration.rbd_user,
            clustername=self.configuration.rbd_cluster_name,
            conffile=self.configuration.rbd_ceph_conf)
        if pool is not None:
            pool = utils.convert_str(pool)
        else:
            pool = self.configuration.rbd_pool

        try:
            if self.configuration.rados_connect_timeout >= 0:
                client.connect(timeout=
                               self.configuration.rados_connect_timeout)
            else:
                client.connect()
            ioctx = client.open_ioctx(pool)
            return client, ioctx
        except self.rados.Error:
            msg = _("Error connecting to ceph cluster.")
            LOG.exception(msg)
            client.shutdown()
            raise exception.VolumeBackendAPIException(data=msg)
Пример #11
0
    def _connect_to_rados(self, pool=None):
        LOG.debug("opening connection to ceph cluster (timeout=%s).",
                  self.configuration.rados_connect_timeout)

        # NOTE (e0ne): rados is binding to C lbirary librados.
        # It blocks eventlet loop so we need to run it in a native
        # python thread.
        client = tpool.Proxy(
            self.rados.Rados(
                rados_id=self.configuration.rbd_user,
                clustername=self.configuration.rbd_cluster_name,
                conffile=self.configuration.rbd_ceph_conf))
        if pool is not None:
            pool = utils.convert_str(pool)
        else:
            pool = self.configuration.rbd_pool

        try:
            if self.configuration.rados_connect_timeout >= 0:
                client.connect(timeout=
                               self.configuration.rados_connect_timeout)
            else:
                client.connect()
            ioctx = client.open_ioctx(pool)
            return client, ioctx
        except self.rados.Error:
            msg = _("Error connecting to ceph cluster.")
            LOG.exception(msg)
            client.shutdown()
            raise exception.VolumeBackendAPIException(data=msg)
Пример #12
0
    def _clone(self, volume, src_pool, src_image, src_snap):
        LOG.debug('cloning %(pool)s/%(img)s@%(snap)s to %(dst)s',
                  dict(pool=src_pool, img=src_image, snap=src_snap,
                       dst=volume.name))

        chunk_size = self.configuration.rbd_store_chunk_size * units.Mi
        order = int(math.log(chunk_size, 2))

        with RADOSClient(self, src_pool) as src_client:
            with RADOSClient(self) as dest_client:
                self.RBDProxy().clone(src_client.ioctx,
                                      utils.convert_str(src_image),
                                      utils.convert_str(src_snap),
                                      dest_client.ioctx,
                                      utils.convert_str(volume.name),
                                      features=src_client.features,
                                      order=order)
Пример #13
0
    def __init__(self, context, db_driver=None, execute=None):
        super(CephBackupDriver, self).__init__(context, db_driver)
        self.rbd = rbd
        self.rados = rados
        self.chunk_size = CONF.backup_ceph_chunk_size
        self._execute = execute or utils.execute

        if self._supports_stripingv2:
            self.rbd_stripe_unit = CONF.backup_ceph_stripe_unit
            self.rbd_stripe_count = CONF.backup_ceph_stripe_count
        else:
            LOG.info(_LI("RBD striping not supported - ignoring configuration " "settings for rbd striping"))
            self.rbd_stripe_count = 0
            self.rbd_stripe_unit = 0

        self._ceph_backup_user = utils.convert_str(CONF.backup_ceph_user)
        self._ceph_backup_pool = utils.convert_str(CONF.backup_ceph_pool)
        self._ceph_backup_conf = utils.convert_str(CONF.backup_ceph_conf)
Пример #14
0
 def _connect_to_rados(self, pool=None):
     """Establish connection to the backup Ceph cluster."""
     client = self.rados.Rados(rados_id=self._ceph_backup_user, conffile=self._ceph_backup_conf)
     try:
         client.connect()
         pool_to_open = utils.convert_str(pool or self._ceph_backup_pool)
         ioctx = client.open_ioctx(pool_to_open)
         return client, ioctx
     except self.rados.Error:
         # shutdown cannot raise an exception
         client.shutdown()
         raise
Пример #15
0
 def _connect_to_rados(self, pool=None):
     """Establish connection to the backup Ceph cluster."""
     client = self.rados.Rados(rados_id=self._ceph_backup_user,
                               conffile=self._ceph_backup_conf)
     try:
         client.connect()
         pool_to_open = utils.convert_str(pool or self._ceph_backup_pool)
         ioctx = client.open_ioctx(pool_to_open)
         return client, ioctx
     except self.rados.Error:
         # shutdown cannot raise an exception
         client.shutdown()
         raise
Пример #16
0
 def copy_volume_to_image(self, context, volume, image_service, image_meta):
     tmp_dir = volume_utils.image_conversion_dir()
     tmp_file = os.path.join(tmp_dir, volume.name + '-' + image_meta['id'])
     with fileutils.remove_path_on_error(tmp_file):
         vol_name = utils.convert_str(volume.name)
         self._try_execute(
             'qemu-img', 'convert', '-f', 'raw', 'vitastor:image=' +
             vol_name.replace(':', '\\:') + self._qemu_args(), '-O', 'raw',
             tmp_file)
         # FIXME: Copy directly if the destination image is also in Vitastor
         volume_utils.upload_volume(context, image_service, image_meta,
                                    tmp_file, volume)
     os.unlink(tmp_file)
Пример #17
0
    def __init__(self,
                 driver,
                 name,
                 pool=None,
                 snapshot=None,
                 read_only=False):
        client, ioctx = driver._connect_to_rados(pool)
        if snapshot is not None:
            snapshot = utils.convert_str(snapshot)

        try:
            self.volume = driver.rbd.Image(ioctx,
                                           utils.convert_str(name),
                                           snapshot=snapshot,
                                           read_only=read_only)
        except driver.rbd.Error:
            LOG.exception(_LE("error opening rbd image %s"), name)
            driver._disconnect_from_rados(client, ioctx)
            raise
        self.driver = driver
        self.client = client
        self.ioctx = ioctx
Пример #18
0
    def delete_snapshot(self, snapshot):
        """Deletes an rbd snapshot."""
        # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are
        #                utf-8 otherwise librbd will barf.
        volume_name = utils.convert_str(snapshot["volume_name"])
        snap_name = utils.convert_str(snapshot["name"])

        with RBDVolumeProxy(self, volume_name) as volume:
            try:
                volume.unprotect_snap(snap_name)
            except self.rbd.ImageBusy:
                children_list = self._get_children_info(volume, snap_name)

                if children_list:
                    for (pool, image) in children_list:
                        LOG.info(
                            _LI("Image %(pool)s/%(image)s is dependent " "on the snapshot %(snap)s."),
                            {"pool": pool, "image": image, "snap": snap_name},
                        )

                raise exception.SnapshotIsBusy(snapshot_name=snap_name)
            volume.remove_snap(snap_name)
Пример #19
0
 def _get_existing_name(existing_ref):
     if not isinstance(existing_ref, dict):
         existing_ref = {"source-name": existing_ref}
     if 'source-name' not in existing_ref:
         reason = _('Reference must contain source-name element.')
         raise exception.ManageExistingInvalidReference(
             existing_ref=existing_ref, reason=reason)
     src_name = utils.convert_str(existing_ref['source-name'])
     if not src_name:
         reason = _('Reference must contain source-name element.')
         raise exception.ManageExistingInvalidReference(
             existing_ref=existing_ref, reason=reason)
     return src_name
Пример #20
0
    def create_volume(self, volume):
        """Creates a logical volume."""
        size = int(volume["size"]) * units.Gi

        LOG.debug("creating volume '%s'", volume["name"])

        chunk_size = self.configuration.rbd_store_chunk_size * units.Mi
        order = int(math.log(chunk_size, 2))

        with RADOSClient(self) as client:
            self.RBDProxy().create(
                client.ioctx, utils.convert_str(volume["name"]), size, order, old_format=False, features=client.features
            )
Пример #21
0
    def manage_existing_snapshot_get_size(self, snapshot, existing_ref):
        """Return size of an existing image for manage_existing.

        :param snapshot: snapshot ref info to be set
        :param existing_ref: {'source-name': <name of snapshot>}
        """
        vol_name = utils.convert_str(snapshot.volume_name)
        snap_name = self._get_existing_name(existing_ref)
        vol = self._get_image(vol_name + '@' + snap_name)
        if not vol:
            raise exception.ManageExistingInvalidReference(
                existing_ref=snapshot_name,
                reason='Specified snapshot does not exist.')
        return int(math.ceil(float(vol['cfg']['size']) / units.Gi))
Пример #22
0
    def revert_to_snapshot(self, context, volume, snapshot):
        """Revert a volume to a given snapshot."""

        vol_name = utils.convert_str(snapshot.volume_name)
        snap_name = utils.convert_str(snapshot.name)

        # Delete the image and recreate it from the snapshot
        args = ['vitastor-cli', 'rm', vol_name, *(self._vitastor_args())]
        try:
            self._execute(*args)
        except processutils.ProcessExecutionError as exc:
            LOG.error("Failed to delete image " + vol_name + ": " + exc)
            raise exception.VolumeBackendAPIException(data=exc.stderr)
        args = [
            'vitastor-cli', 'create', '--parent', vol_name + '@' + snap_name,
            vol_name, *(self._vitastor_args())
        ]
        try:
            self._execute(*args)
        except processutils.ProcessExecutionError as exc:
            LOG.error("Failed to recreate image " + vol_name + " from " +
                      vol_name + "@" + snap_name + ": " + exc)
            raise exception.VolumeBackendAPIException(data=exc.stderr)
Пример #23
0
    def create_volume_from_snapshot(self, volume, snapshot):
        """Creates a cloned volume from an existing snapshot."""

        vol_name = utils.convert_str(volume.name)
        snap_name = utils.convert_str(snapshot.name)

        snap = self._get_image(vol_name + '@' + snap_name)
        if not snap:
            raise exception.SnapshotNotFound(snapshot_id=snap_name)
        snap_inode_id = int(resp['responses'][0]['kvs'][0]['value']['id'])
        snap_pool_id = int(resp['responses'][0]['kvs'][0]['value']['pool_id'])

        size = snap['cfg']['size']
        if int(volume.size):
            size = int(volume.size) * units.Gi
        new_cfg = self._create_image(
            vol_name, {
                'size': size,
                'parent_id': snap['idx']['id'],
                'parent_pool_id': snap['idx']['pool_id'],
            })

        return {}
Пример #24
0
    def __init__(self, *args, **kwargs):
        super(RBDDriver, self).__init__(*args, **kwargs)
        self.configuration.append_config_values(rbd_opts)
        self._stats = {}
        # allow overrides for testing
        self.rados = kwargs.get("rados", rados)
        self.rbd = kwargs.get("rbd", rbd)

        # All string args used with librbd must be None or utf-8 otherwise
        # librbd will break.
        for attr in ["rbd_cluster_name", "rbd_user", "rbd_ceph_conf", "rbd_pool"]:
            val = getattr(self.configuration, attr)
            if val is not None:
                setattr(self.configuration, attr, utils.convert_str(val))
Пример #25
0
    def delete_snapshot(self, snapshot):
        """Deletes an rbd snapshot."""
        # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are
        #                utf-8 otherwise librbd will barf.
        volume_name = utils.convert_str(snapshot.volume_name)
        snap_name = utils.convert_str(snapshot.name)

        with RBDVolumeProxy(self, volume_name) as volume:
            try:
                volume.unprotect_snap(snap_name)
            except self.rbd.InvalidArgument:
                LOG.info(
                    _LI("InvalidArgument: Unable to unprotect snapshot %s."),
                    snap_name)
            except self.rbd.ImageNotFound:
                LOG.info(
                    _LI("ImageNotFound: Unable to unprotect snapshot %s."),
                    snap_name)
            except self.rbd.ImageBusy:
                children_list = self._get_children_info(volume, snap_name)

                if children_list:
                    for (pool, image) in children_list:
                        LOG.info(
                            _LI('Image %(pool)s/%(image)s is dependent '
                                'on the snapshot %(snap)s.'), {
                                    'pool': pool,
                                    'image': image,
                                    'snap': snap_name
                                })

                raise exception.SnapshotIsBusy(snapshot_name=snap_name)
            try:
                volume.remove_snap(snap_name)
            except self.rbd.ImageNotFound:
                LOG.info(_LI("Snapshot %s does not exist in backend."),
                         snap_name)
Пример #26
0
    def __init__(self, *args, **kwargs):
        super(RBDDriver, self).__init__(*args, **kwargs)
        self.configuration.append_config_values(rbd_opts)
        self._stats = {}
        # allow overrides for testing
        self.rados = kwargs.get('rados', rados)
        self.rbd = kwargs.get('rbd', rbd)

        # All string args used with librbd must be None or utf-8 otherwise
        # librbd will break.
        for attr in ['rbd_cluster_name', 'rbd_user',
                     'rbd_ceph_conf', 'rbd_pool']:
            val = getattr(self.configuration, attr)
            if val is not None:
                setattr(self.configuration, attr, utils.convert_str(val))
Пример #27
0
    def create_volume(self, volume):
        """Creates a logical volume."""
        size = int(volume['size']) * units.Gi

        LOG.debug("creating volume '%s'", volume['name'])

        chunk_size = self.configuration.rbd_store_chunk_size * units.Mi
        order = int(math.log(chunk_size, 2))

        with RADOSClient(self) as client:
            self.RBDProxy().create(client.ioctx,
                                   utils.convert_str(volume['name']),
                                   size,
                                   order,
                                   old_format=False,
                                   features=client.features)
Пример #28
0
    def manage_existing_get_size(self, volume, existing_ref):
        """Return size of an existing image for manage_existing.

        :param volume:
            volume ref info to be set
        :param existing_ref:
            existing_ref is a dictionary of the form:
            {'source-name': <name of rbd image>}
        """

        # Check that the reference is valid
        if 'source-name' not in existing_ref:
            reason = _('Reference must contain source-name element.')
            raise exception.ManageExistingInvalidReference(
                existing_ref=existing_ref, reason=reason)

        rbd_name = utils.convert_str(existing_ref['source-name'])

        with RADOSClient(self) as client:
            # Raise an exception if we didn't find a suitable rbd image.
            try:
                rbd_image = self.rbd.Image(client.ioctx, rbd_name)
                image_size = rbd_image.size()
            except self.rbd.ImageNotFound:
                kwargs = {
                    'existing_ref': rbd_name,
                    'reason': 'Specified rbd image does not exist.'
                }
                raise exception.ManageExistingInvalidReference(**kwargs)
            finally:
                rbd_image.close()

            # RBD image size is returned in bytes.  Attempt to parse
            # size as a float and round up to the next integer.
            try:
                convert_size = int(math.ceil(int(image_size))) / units.Gi
                return convert_size
            except ValueError:
                exception_message = (_("Failed to manage existing volume "
                                       "%(name)s, because reported size "
                                       "%(size)s was not a floating-point"
                                       " number.") % {
                                           'name': rbd_name,
                                           'size': image_size
                                       })
                raise exception.VolumeBackendAPIException(
                    data=exception_message)
Пример #29
0
    def manage_existing_get_size(self, volume, existing_ref):
        """Return size of an existing image for manage_existing.

        :param volume:
            volume ref info to be set
        :param existing_ref:
            existing_ref is a dictionary of the form:
            {'source-name': <name of rbd image>}
        """

        # Check that the reference is valid
        if 'source-name' not in existing_ref:
            reason = _('Reference must contain source-name element.')
            raise exception.ManageExistingInvalidReference(
                existing_ref=existing_ref, reason=reason)

        rbd_name = utils.convert_str(existing_ref['source-name'])

        with RADOSClient(self) as client:
            # Raise an exception if we didn't find a suitable rbd image.
            try:
                rbd_image = self.rbd.Image(client.ioctx, rbd_name)
                image_size = rbd_image.size()
            except self.rbd.ImageNotFound:
                kwargs = {'existing_ref': rbd_name,
                          'reason': 'Specified rbd image does not exist.'}
                raise exception.ManageExistingInvalidReference(**kwargs)
            finally:
                rbd_image.close()

            # RBD image size is returned in bytes.  Attempt to parse
            # size as a float and round up to the next integer.
            try:
                convert_size = int(math.ceil(int(image_size))) / units.Gi
                return convert_size
            except ValueError:
                exception_message = (_("Failed to manage existing volume "
                                       "%(name)s, because reported size "
                                       "%(size)s was not a floating-point"
                                       " number.")
                                     % {'name': rbd_name,
                                        'size': image_size})
                raise exception.VolumeBackendAPIException(
                    data=exception_message)
Пример #30
0
    def create_volume(self, volume):
        """Creates a logical volume."""

        size = int(volume.size) * units.Gi
        # FIXME: Check if convert_str is really required
        vol_name = utils.convert_str(volume.name)
        if vol_name.find('@') >= 0 or vol_name.find('/') >= 0:
            raise exception.VolumeBackendAPIException(
                data='@ and / are forbidden in volume and snapshot names')

        LOG.debug("creating volume '%s'", vol_name)

        self._create_image(vol_name, {'size': size})

        if volume.encryption_key_id:
            self._create_encrypted_volume(volume, volume.obj_context)

        volume_update = {}
        return volume_update
Пример #31
0
 def extend_volume(self, volume, new_size):
     """Extend an existing volume."""
     vol_name = utils.convert_str(volume.name)
     while True:
         vol = self._get_image(vol_name)
         if not vol:
             raise exception.VolumeBackendAPIException(
                 data='Volume ' + vol_name + ' does not exist')
         # change size
         size = int(new_size) * units.Gi
         if size == vol['cfg']['size']:
             break
         resp = self._etcd_txn({
             'compare': [{
                 'target':
                 'MOD',
                 'mod_revision':
                 vol['cfg_mod'],
                 'key':
                 'config/inode/' + str(vol['idx']['pool_id']) + '/' +
                 str(vol['idx']['id']),
             }],
             'success': [
                 {
                     'request_put': {
                         'key':
                         'config/inode/' + str(vol['idx']['pool_id']) +
                         '/' + str(vol['idx']['id']),
                         'value':
                         json.dumps({
                             **vol['cfg'], 'size': size
                         }),
                     }
                 },
             ]
         })
         if resp.get('succeeded'):
             break
     LOG.debug("Extend volume from %(old_size)s GB to %(new_size)s GB.", {
         'old_size': volume.size,
         'new_size': new_size
     })
Пример #32
0
    def create_volume(self, volume):
        """Creates a logical volume."""

#        if volume.encryption_key_id:
#            message = _("Encryption is not yet supported.")
#            raise exception.VolumeDriverException(message=message)
        size = int(volume.size) * units.Gi

        LOG.debug("creating volume '%s'", volume.name)

        chunk_size = self.configuration.rbd_store_chunk_size * units.Mi
        order = int(math.log(chunk_size, 2))

        with RADOSClient(self) as client:
            self.RBDProxy().create(client.ioctx,
                                   utils.convert_str(volume.name),
                                   size,
                                   order,
                                   old_format=False,
                                   features=client.features)
Пример #33
0
 def _synchronized(f, *a, **k) -> Callable:
     call_args = inspect.getcallargs(f, *a, **k)
     call_args['f_name'] = f.__name__
     lock = coordinator.get_lock(lock_name.format(**call_args))
     name = utils.convert_str(lock.name)
     f_name = f.__name__
     t1 = timeutils.now()
     t2 = None
     try:
         LOG.debug(f'Acquiring lock "{name}" by "{f_name}"')
         with lock(blocking):
             t2 = timeutils.now()
             LOG.debug(f'Lock "{name}" acquired by "{f_name}" :: '
                       f'waited {t2 - t1:0.3f}s')
             return f(*a, **k)
     finally:
         t3 = timeutils.now()
         if t2 is None:
             held_secs = "N/A"
         else:
             held_secs = "%0.3fs" % (t3 - t2)
         LOG.debug(
             f'Lock "{name}" released by "{f_name}" :: held {held_secs}')
Пример #34
0
    def create_cloned_volume(self, volume, src_vref):
        """Create a cloned volume from another volume.

        Since we are cloning from a volume and not a snapshot, we must first
        create a snapshot of the source volume.

        The user has the option to limit how long a volume's clone chain can be
        by setting rbd_max_clone_depth. If a clone is made of another clone
        and that clone has rbd_max_clone_depth clones behind it, the source
        volume will be flattened.
        """
        src_name = utils.convert_str(src_vref['name'])
        dest_name = utils.convert_str(volume['name'])
        flatten_parent = False

        # Do full copy if requested
        if self.configuration.rbd_max_clone_depth <= 0:
            with RBDVolumeProxy(self, src_name, read_only=True) as vol:
                vol.copy(vol.ioctx, dest_name)

            return

        # Otherwise do COW clone.
        with RADOSClient(self) as client:
            depth = self._get_clone_depth(client, src_name)
            # If source volume is a clone and rbd_max_clone_depth reached,
            # flatten the source before cloning. Zero rbd_max_clone_depth means
            # infinite is allowed.
            if depth == self.configuration.rbd_max_clone_depth:
                LOG.debug("maximum clone depth (%d) has been reached - "
                          "flattening source volume",
                          self.configuration.rbd_max_clone_depth)
                flatten_parent = True

            src_volume = self.rbd.Image(client.ioctx, src_name)
            try:
                # First flatten source volume if required.
                if flatten_parent:
                    _pool, parent, snap = self._get_clone_info(src_volume,
                                                               src_name)
                    # Flatten source volume
                    LOG.debug("flattening source volume %s", src_name)
                    src_volume.flatten()
                    # Delete parent clone snap
                    parent_volume = self.rbd.Image(client.ioctx, parent)
                    try:
                        parent_volume.unprotect_snap(snap)
                        parent_volume.remove_snap(snap)
                    finally:
                        parent_volume.close()

                # Create new snapshot of source volume
                clone_snap = "%s.clone_snap" % dest_name
                LOG.debug("creating snapshot='%s'", clone_snap)
                src_volume.create_snap(clone_snap)
                src_volume.protect_snap(clone_snap)
            except Exception:
                # Only close if exception since we still need it.
                src_volume.close()
                raise

            # Now clone source volume snapshot
            try:
                LOG.debug("cloning '%(src_vol)s@%(src_snap)s' to "
                          "'%(dest)s'",
                          {'src_vol': src_name, 'src_snap': clone_snap,
                           'dest': dest_name})
                self.RBDProxy().clone(client.ioctx, src_name, clone_snap,
                                      client.ioctx, dest_name,
                                      features=client.features)
            except Exception:
                src_volume.unprotect_snap(clone_snap)
                src_volume.remove_snap(clone_snap)
                raise
            finally:
                src_volume.close()

        if volume['size'] != src_vref['size']:
            LOG.debug("resize volume '%(dst_vol)s' from %(src_size)d to "
                      "%(dst_size)d",
                      {'dst_vol': volume['name'], 'src_size': src_vref['size'],
                       'dst_size': volume['size']})
            self._resize(volume)

        LOG.debug("clone created successfully")
Пример #35
0
    def delete_volume(self, volume):
        """Deletes a logical volume."""

        vol_name = utils.convert_str(volume.name)

        # Find the volume and all its snapshots
        range_end = b'index/image/' + vol_name.encode('utf-8')
        range_end = range_end[0:len(range_end) -
                              1] + six.int2byte(range_end[len(range_end) - 1] +
                                                1)
        resp = self._etcd_txn({
            'success': [
                {
                    'request_range': {
                        'key': 'index/image/' + vol_name,
                        'range_end': range_end
                    }
                },
            ]
        })
        if len(resp['responses'][0]['kvs']) == 0:
            # already deleted
            LOG.info("volume %s no longer exists in backend", vol_name)
            return
        layers = resp['responses'][0]['kvs']
        layer_ids = {}
        for kv in layers:
            inode_id = int(kv['value']['id'])
            pool_id = int(kv['value']['pool_id'])
            inode_pool_id = (pool_id << 48) | (inode_id & 0xffffffffffff)
            layer_ids[inode_pool_id] = True

        # Check if the volume has clones and raise 'busy' if so
        children = self._child_count(layer_ids)
        if children > 0:
            raise exception.VolumeIsBusy(volume_name=vol_name)

        # Clear data
        for kv in layers:
            args = [
                'vitastor-cli', 'rm-data', '--pool',
                str(kv['value']['pool_id']), '--inode',
                str(kv['value']['id']), '--progress', '0',
                *(self._vitastor_args())
            ]
            try:
                self._execute(*args)
            except processutils.ProcessExecutionError as exc:
                LOG.error("Failed to remove layer " + kv['key'] + ": " + exc)
                raise exception.VolumeBackendAPIException(data=exc.stderr)

        # Delete all layers from etcd
        requests = []
        for kv in layers:
            requests.append({'request_delete_range': {'key': kv['key']}})
            requests.append({
                'request_delete_range': {
                    'key':
                    'config/inode/' + str(kv['value']['pool_id']) + '/' +
                    str(kv['value']['id'])
                }
            })
        self._etcd_txn({'success': requests})
Пример #36
0
 def create_snapshot(self, snapshot):
     """Creates an rbd snapshot."""
     with RBDVolumeProxy(self, snapshot['volume_name']) as volume:
         snap = utils.convert_str(snapshot['name'])
         volume.create_snap(snap)
         volume.protect_snap(snap)
Пример #37
0
    def delete_volume(self, volume):
        """Deletes a logical volume."""
        # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are
        #                utf-8 otherwise librbd will barf.
        volume_name = utils.convert_str(volume['name'])
        with RADOSClient(self) as client:
            try:
                rbd_image = self.rbd.Image(client.ioctx, volume_name)
            except self.rbd.ImageNotFound:
                LOG.info(_LI("volume %s no longer exists in backend"),
                         volume_name)
                return

            clone_snap = None
            parent = None

            # Ensure any backup snapshots are deleted
            self._delete_backup_snaps(rbd_image)

            # If the volume has non-clone snapshots this delete is expected to
            # raise VolumeIsBusy so do so straight away.
            try:
                snaps = rbd_image.list_snaps()
                for snap in snaps:
                    if snap['name'].endswith('.clone_snap'):
                        LOG.debug("volume has clone snapshot(s)")
                        # We grab one of these and use it when fetching parent
                        # info in case the volume has been flattened.
                        clone_snap = snap['name']
                        break

                    raise exception.VolumeIsBusy(volume_name=volume_name)

                # Determine if this volume is itself a clone
                _pool, parent, parent_snap = self._get_clone_info(
                    rbd_image, volume_name, clone_snap)
            finally:
                rbd_image.close()

            @utils.retry(self.rbd.ImageBusy, retries=3)
            def _try_remove_volume(client, volume_name):
                self.RBDProxy().remove(client.ioctx, volume_name)

            if clone_snap is None:
                LOG.debug("deleting rbd volume %s", volume_name)
                try:
                    _try_remove_volume(client, volume_name)
                except self.rbd.ImageBusy:
                    msg = (_("ImageBusy error raised while deleting rbd "
                             "volume. This may have been caused by a "
                             "connection from a client that has crashed and, "
                             "if so, may be resolved by retrying the delete "
                             "after 30 seconds has elapsed."))
                    LOG.warning(msg)
                    # Now raise this so that volume stays available so that we
                    # delete can be retried.
                    raise exception.VolumeIsBusy(msg, volume_name=volume_name)
                except self.rbd.ImageNotFound:
                    LOG.info(
                        _LI("RBD volume %s not found, allowing delete "
                            "operation to proceed."), volume_name)
                    return

                # If it is a clone, walk back up the parent chain deleting
                # references.
                if parent:
                    LOG.debug("volume is a clone so cleaning references")
                    self._delete_clone_parent_refs(client, parent, parent_snap)
            else:
                # If the volume has copy-on-write clones we will not be able to
                # delete it. Instead we will keep it as a silent volume which
                # will be deleted when it's snapshot and clones are deleted.
                new_name = "%s.deleted" % (volume_name)
                self.RBDProxy().rename(client.ioctx, volume_name, new_name)
Пример #38
0
 def create_snapshot(self, snapshot):
     """Creates an rbd snapshot."""
     with RBDVolumeProxy(self, snapshot['volume_name']) as volume:
         snap = utils.convert_str(snapshot['name'])
         volume.create_snap(snap)
         volume.protect_snap(snap)
Пример #39
0
    def create_cloned_volume(self, volume, src_vref):
        """Create a cloned volume from another volume.

        Since we are cloning from a volume and not a snapshot, we must first
        create a snapshot of the source volume.

        The user has the option to limit how long a volume's clone chain can be
        by setting rbd_max_clone_depth. If a clone is made of another clone
        and that clone has rbd_max_clone_depth clones behind it, the source
        volume will be flattened.
        """
        src_name = utils.convert_str(src_vref['name'])
        dest_name = utils.convert_str(volume['name'])
        flatten_parent = False

        # Do full copy if requested
        if self.configuration.rbd_max_clone_depth <= 0:
            with RBDVolumeProxy(self, src_name, read_only=True) as vol:
                vol.copy(vol.ioctx, dest_name)

            return

        # Otherwise do COW clone.
        with RADOSClient(self) as client:
            depth = self._get_clone_depth(client, src_name)
            # If source volume is a clone and rbd_max_clone_depth reached,
            # flatten the source before cloning. Zero rbd_max_clone_depth means
            # infinite is allowed.
            if depth == self.configuration.rbd_max_clone_depth:
                LOG.debug(
                    "maximum clone depth (%d) has been reached - "
                    "flattening source volume",
                    self.configuration.rbd_max_clone_depth)
                flatten_parent = True

            src_volume = self.rbd.Image(client.ioctx, src_name)
            try:
                # First flatten source volume if required.
                if flatten_parent:
                    _pool, parent, snap = self._get_clone_info(
                        src_volume, src_name)
                    # Flatten source volume
                    LOG.debug("flattening source volume %s", src_name)
                    src_volume.flatten()
                    # Delete parent clone snap
                    parent_volume = self.rbd.Image(client.ioctx, parent)
                    try:
                        parent_volume.unprotect_snap(snap)
                        parent_volume.remove_snap(snap)
                    finally:
                        parent_volume.close()

                # Create new snapshot of source volume
                clone_snap = "%s.clone_snap" % dest_name
                LOG.debug("creating snapshot='%s'", clone_snap)
                src_volume.create_snap(clone_snap)
                src_volume.protect_snap(clone_snap)
            except Exception:
                # Only close if exception since we still need it.
                src_volume.close()
                raise

            # Now clone source volume snapshot
            try:
                LOG.debug(
                    "cloning '%(src_vol)s@%(src_snap)s' to "
                    "'%(dest)s'", {
                        'src_vol': src_name,
                        'src_snap': clone_snap,
                        'dest': dest_name
                    })
                self.RBDProxy().clone(client.ioctx,
                                      src_name,
                                      clone_snap,
                                      client.ioctx,
                                      dest_name,
                                      features=client.features)
            except Exception:
                src_volume.unprotect_snap(clone_snap)
                src_volume.remove_snap(clone_snap)
                raise
            finally:
                src_volume.close()

        if volume['size'] != src_vref['size']:
            LOG.debug(
                "resize volume '%(dst_vol)s' from %(src_size)d to "
                "%(dst_size)d", {
                    'dst_vol': volume['name'],
                    'src_size': src_vref['size'],
                    'dst_size': volume['size']
                })
            self._resize(volume)

        LOG.debug("clone created successfully")
Пример #40
0
 def _get_new_snap_name(self, backup_id):
     return utils.convert_str("backup.%s.snap.%s"
                              % (backup_id, time.time()))
Пример #41
0
 def __init__(self, image, pool, user, conf):
     self.image = image
     self.pool = utils.convert_str(pool)
     self.user = utils.convert_str(user)
     self.conf = utils.convert_str(conf)
Пример #42
0
    def _process_stack(self, request, action, action_args,
                       content_type, body, accept):
        """Implement the processing stack."""

        # Get the implementing method
        try:
            meth, extensions = self.get_method(request, action,
                                               content_type, body)
        except (AttributeError, TypeError):
            return Fault(webob.exc.HTTPNotFound())
        except KeyError as ex:
            msg = _("There is no such action: %s") % ex.args[0]
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))
        except exception.MalformedRequestBody:
            msg = _("Malformed request body")
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))

        if body:
            decoded_body = encodeutils.safe_decode(body, errors='ignore')
            msg = ("Action: '%(action)s', calling method: %(meth)s, body: "
                   "%(body)s") % {'action': action,
                                  'body': six.text_type(decoded_body),
                                  'meth': six.text_type(meth)}
            LOG.debug(strutils.mask_password(msg))
        else:
            LOG.debug("Calling method '%(meth)s'",
                      {'meth': six.text_type(meth)})

        # Now, deserialize the request body...
        try:
            if content_type:
                contents = self.deserialize(meth, content_type, body)
            else:
                contents = {}
        except exception.InvalidContentType:
            msg = _("Unsupported Content-Type")
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))
        except exception.MalformedRequestBody:
            msg = _("Malformed request body")
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))

        # Update the action args
        action_args.update(contents)

        project_id = action_args.pop("project_id", None)
        context = request.environ.get('cinder.context')
        if (context and project_id and (project_id != context.project_id)):
            msg = _("Malformed request url")
            return Fault(webob.exc.HTTPBadRequest(explanation=msg))

        # Run pre-processing extensions
        response, post = self.pre_process_extensions(extensions,
                                                     request, action_args)

        if not response:
            try:
                with ResourceExceptionHandler():
                    action_result = self.dispatch(meth, request, action_args)
            except Fault as ex:
                response = ex

        if not response:
            # No exceptions; convert action_result into a
            # ResponseObject
            resp_obj = None
            if isinstance(action_result, dict) or action_result is None:
                resp_obj = ResponseObject(action_result)
            elif isinstance(action_result, ResponseObject):
                resp_obj = action_result
            else:
                response = action_result

            # Run post-processing extensions
            if resp_obj:
                _set_request_id_header(request, resp_obj)
                # Do a preserialize to set up the response object
                serializers = getattr(meth, 'wsgi_serializers', {})
                resp_obj._bind_method_serializers(serializers)
                if hasattr(meth, 'wsgi_code'):
                    resp_obj._default_code = meth.wsgi_code
                resp_obj.preserialize(accept, self.default_serializers)

                # Process post-processing extensions
                response = self.post_process_extensions(post, resp_obj,
                                                        request, action_args)

            if resp_obj and not response:
                response = resp_obj.serialize(request, accept,
                                              self.default_serializers)

        try:
            msg_dict = dict(url=request.url, status=response.status_int)
            msg = _LI("%(url)s returned with HTTP %(status)d")
        except AttributeError as e:
            msg_dict = dict(url=request.url, e=e)
            msg = _LI("%(url)s returned a fault: %(e)s")

        LOG.info(msg, msg_dict)

        if hasattr(response, 'headers'):
            for hdr, val in response.headers.items():
                # Headers must be utf-8 strings
                val = utils.convert_str(val)

                response.headers[hdr] = val

            if (not request.api_version_request.is_null() and
               not _is_legacy_endpoint(request)):
                response.headers[API_VERSION_REQUEST_HEADER] = (
                    VOLUME_SERVICE + ' ' +
                    request.api_version_request.get_string())
                response.headers['Vary'] = API_VERSION_REQUEST_HEADER

        return response
Пример #43
0
    def delete_volume(self, volume):
        """Deletes a logical volume."""
        # NOTE(dosaboy): this was broken by commit cbe1d5f. Ensure names are
        #                utf-8 otherwise librbd will barf.
        volume_name = utils.convert_str(volume['name'])
        with RADOSClient(self) as client:
            try:
                rbd_image = self.rbd.Image(client.ioctx, volume_name)
            except self.rbd.ImageNotFound:
                LOG.info(_LI("volume %s no longer exists in backend"),
                         volume_name)
                return

            clone_snap = None
            parent = None

            # Ensure any backup snapshots are deleted
            self._delete_backup_snaps(rbd_image)

            # If the volume has non-clone snapshots this delete is expected to
            # raise VolumeIsBusy so do so straight away.
            try:
                snaps = rbd_image.list_snaps()
                for snap in snaps:
                    if snap['name'].endswith('.clone_snap'):
                        LOG.debug("volume has clone snapshot(s)")
                        # We grab one of these and use it when fetching parent
                        # info in case the volume has been flattened.
                        clone_snap = snap['name']
                        break

                    raise exception.VolumeIsBusy(volume_name=volume_name)

                # Determine if this volume is itself a clone
                _pool, parent, parent_snap = self._get_clone_info(rbd_image,
                                                                  volume_name,
                                                                  clone_snap)
            finally:
                rbd_image.close()

            if clone_snap is None:
                LOG.debug("deleting rbd volume %s", volume_name)
                try:
                    self.RBDProxy().remove(client.ioctx, volume_name)
                except self.rbd.ImageBusy:
                    msg = (_("ImageBusy error raised while deleting rbd "
                             "volume. This may have been caused by a "
                             "connection from a client that has crashed and, "
                             "if so, may be resolved by retrying the delete "
                             "after 30 seconds has elapsed."))
                    LOG.warning(msg)
                    # Now raise this so that volume stays available so that we
                    # delete can be retried.
                    raise exception.VolumeIsBusy(msg, volume_name=volume_name)
                except self.rbd.ImageNotFound:
                    LOG.info(_LI("RBD volume %s not found, allowing delete "
                                 "operation to proceed."), volume_name)
                    return

                # If it is a clone, walk back up the parent chain deleting
                # references.
                if parent:
                    LOG.debug("volume is a clone so cleaning references")
                    self._delete_clone_parent_refs(client, parent, parent_snap)
            else:
                # If the volume has copy-on-write clones we will not be able to
                # delete it. Instead we will keep it as a silent volume which
                # will be deleted when it's snapshot and clones are deleted.
                new_name = "%s.deleted" % (volume_name)
                self.RBDProxy().rename(client.ioctx, volume_name, new_name)
Пример #44
0
 def name(self):
     return utils.convert_str("backup.%s.meta" % self._backup_id)
Пример #45
0
 def __init__(self, image, pool, user, conf):
     self.image = image
     self.pool = utils.convert_str(pool)
     self.user = utils.convert_str(user)
     self.conf = utils.convert_str(conf)
Пример #46
0
    def _try_delete_base_image(self, backup_id, volume_id, base_name=None):
        """Try to delete backup RBD image.

        If the rbd image is a base image for incremental backups, it may have
        snapshots. Delete the snapshot associated with backup_id and if the
        image has no more snapshots, delete it. Otherwise return.

        If no base name is provided try normal (full) format then diff format
        image name.

        If a base name is provided but does not exist, ImageNotFound will be
        raised.

        If the image is busy, a number of retries will be performed if
        ImageBusy is received, after which the exception will be propagated to
        the caller.
        """
        retries = 3
        delay = 5
        try_diff_format = False

        if base_name is None:
            try_diff_format = True

            base_name = self._get_backup_base_name(volume_id, backup_id)
            LOG.debug("Trying diff format basename='%(basename)s' for "
                      "backup base image of volume %(volume)s.",
                      {'basename': base_name, 'volume': volume_id})

        with rbd_driver.RADOSClient(self) as client:
            rbd_exists, base_name = \
                self._rbd_image_exists(base_name, volume_id, client,
                                       try_diff_format=try_diff_format)
            if not rbd_exists:
                raise self.rbd.ImageNotFound(_("image %s not found") %
                                             base_name)

            while retries >= 0:
                # First delete associated snapshot from base image (if exists)
                snap, rem = self._delete_backup_snapshot(client, base_name,
                                                         backup_id)
                if rem:
                    LOG.info(
                        _LI("Backup base image of volume %(volume)s still "
                            "has %(snapshots)s snapshots so skipping base "
                            "image delete."),
                        {'snapshots': rem, 'volume': volume_id})
                    return

                LOG.info(_LI("Deleting backup base image='%(basename)s' of "
                             "volume %(volume)s."),
                         {'basename': base_name, 'volume': volume_id})
                # Delete base if no more snapshots
                try:
                    self.rbd.RBD().remove(client.ioctx, base_name)
                except self.rbd.ImageBusy:
                    # Allow a retry if the image is busy
                    if retries > 0:
                        LOG.info(_LI("Backup image of volume %(volume)s is "
                                     "busy, retrying %(retries)s more time(s) "
                                     "in %(delay)ss."),
                                 {'retries': retries,
                                  'delay': delay,
                                  'volume': volume_id})
                        eventlet.sleep(delay)
                    else:
                        LOG.error(_LE("Max retries reached deleting backup "
                                      "%(basename)s image of volume "
                                      "%(volume)s."),
                                  {'volume': volume_id,
                                   'basename': base_name})
                        raise
                else:
                    LOG.debug("Base backup image='%(basename)s' of volume "
                              "%(volume)s deleted.",
                              {'basename': base_name, 'volume': volume_id})
                    retries = 0
                finally:
                    retries -= 1

            # Since we have deleted the base image we can delete the source
            # volume backup snapshot.
            src_name = utils.convert_str(volume_id)
            if src_name in self.rbd.RBD().list(client.ioctx):
                LOG.debug("Deleting source volume snapshot '%(snapshot)s' "
                          "for backup %(basename)s.",
                          {'snapshot': snap, 'basename': base_name})
                src_rbd = self.rbd.Image(client.ioctx, src_name)
                try:
                    src_rbd.remove_snap(snap)
                finally:
                    src_rbd.close()