_RBD_LOCK_MODE_EXCLUSIVE "RBD_LOCK_MODE_EXCLUSIVE"
_RBD_LOCK_MODE_SHARED "RBD_LOCK_MODE_SHARED"
+ ctypedef enum rbd_trash_image_source_t:
+ _RBD_TRASH_IMAGE_SOURCE_USER "RBD_TRASH_IMAGE_SOURCE_USER",
+ _RBD_TRASH_IMAGE_SOURCE_MIRRORING "RBD_TRASH_IMAGE_SOURCE_MIRRORING"
+
+ ctypedef struct rbd_trash_image_info_t:
+ char *id
+ char *name
+ rbd_trash_image_source_t source
+ time_t deletion_time
+ time_t deferment_end_time
+
ctypedef void (*rbd_callback_t)(rbd_completion_t cb, void *arg)
ctypedef int (*librbd_progress_fn_t)(uint64_t offset, uint64_t total, void* ptr)
int rbd_rename(rados_ioctx_t src_io_ctx, const char *srcname,
const char *destname)
+ int rbd_trash_move(rados_ioctx_t io, const char *name, uint64_t delay)
+ int rbd_trash_list(rados_ioctx_t io, rbd_trash_image_info_t *trash_entries,
+ size_t *num_entries)
+ void rbd_trash_list_cleanup(rbd_trash_image_info_t *trash_entries,
+ size_t num_entries)
+ int rbd_trash_remove(rados_ioctx_t io, const char *id, int force)
+ int rbd_trash_restore(rados_ioctx_t io, const char *id, const char *name)
+
int rbd_mirror_mode_get(rados_ioctx_t io, rbd_mirror_mode_t *mirror_mode)
int rbd_mirror_mode_set(rados_ioctx_t io, rbd_mirror_mode_t mirror_mode)
int rbd_mirror_peer_add(rados_ioctx_t io, char *uuid,
if ret != 0:
raise make_ex(ret, 'error renaming image')
+ def trash_move(self, ioctx, name, delay=0):
+ """
+ Moves an RBD image to the trash.
+ :param ioctx: determines which RADOS pool the image is in
+ :type ioctx: :class:`rados.Ioctx`
+ :param name: the name of the image to remove
+ :type name: str
+ :param delay: time delay in seconds before the image can be deleted
+ from trash
+ :type delay: int
+ :raises: :class:`ImageNotFound`
+ """
+ name = cstr(name, 'name')
+ cdef:
+ rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+ char *_name = name
+ uint64_t _delay = delay
+ with nogil:
+ ret = rbd_trash_move(_ioctx, _name, _delay)
+ if ret != 0:
+ raise make_ex(ret, 'error moving image to trash')
+
+ def trash_remove(self, ioctx, image_id, force=False):
+ """
+ Deletes an RBD image from trash. If image deferment time has not
+ expired :class:`PermissionError` is raised.
+ :param ioctx: determines which RADOS pool the image is in
+ :type ioctx: :class:`rados.Ioctx`
+ :param image_id: the id of the image to remove
+ :type image_id: str
+ :param force: force remove even if deferment time has not expired
+ :type force: bool
+ :raises: :class:`ImageNotFound`, :class:`PermissionError`
+ """
+ image_id = cstr(image_id, 'image_id')
+ cdef:
+ rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+ char *_image_id = image_id
+ int _force = force
+ with nogil:
+ ret = rbd_trash_remove(_ioctx, _image_id, _force)
+ if ret != 0:
+ raise make_ex(ret, 'error deleting image from trash')
+
+ def trash_list(self, ioctx):
+ """
+ Lists all entries from trash.
+ :param ioctx: determines which RADOS pool the image is in
+ :type ioctx: :class:`rados.Ioctx`
+ :returns: :class:`TrashIterator`
+ """
+ return TrashIterator(ioctx)
+
+ def trash_restore(self, ioctx, image_id, name):
+ """
+ Restores an RBD image from trash.
+ :param ioctx: determines which RADOS pool the image is in
+ :type ioctx: :class:`rados.Ioctx`
+ :param image_id: the id of the image to restore
+ :type image_id: str
+ :param name: the new name of the restored image
+ :type name: str
+ :raises: :class:`ImageNotFound`
+ """
+ image_id = cstr(image_id, 'image_id')
+ name = cstr(name, 'name')
+ cdef:
+ rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+ char *_image_id = image_id
+ char *_name = name
+ with nogil:
+ ret = rbd_trash_restore(_ioctx, _image_id, _name)
+ if ret != 0:
+ raise make_ex(ret, 'error restoring image from trash')
+
def mirror_mode_get(self, ioctx):
"""
Get pool mirror mode.
if self.snaps:
rbd_snap_list_end(self.snaps)
free(self.snaps)
+
+cdef class TrashIterator(object):
+ """
+ Iterator over trash entries.
+
+ Yields a dictionary containing trash info of an image.
+
+ Keys are:
+
+ * `id` (str) - image id
+
+ * `name` (str) - image name
+
+ * `source` (str) - source of deletion
+
+ * `deletion_time` (datetime) - time of deletion
+
+ * `deferment_end_time` (datetime) - time that an image is allowed to be
+ removed from trash
+ """
+
+ cdef:
+ rados_ioctx_t ioctx
+ size_t num_entries
+ rbd_trash_image_info_t *entries
+
+ def __init__(self, ioctx):
+ self.ioctx = convert_ioctx(ioctx)
+ self.num_entries = 1024
+ self.entries = <rbd_trash_image_info_t *>realloc_chk(NULL,
+ sizeof(rbd_trash_image_info_t) * self.num_entries)
+ with nogil:
+ ret = rbd_trash_list(self.ioctx, self.entries, &self.num_entries)
+ if ret < 0:
+ raise make_ex(ret, 'error listing trash entries')
+
+ __source_string = ['USER', 'MIRRORING']
+
+ def __iter__(self):
+ for i in range(self.num_entries):
+ yield {
+ 'id' : decode_cstr(self.entries[i].id),
+ 'name' : decode_cstr(self.entries[i].name),
+ 'source' : TrashIterator.__source_string[self.entries[i].source],
+ 'deletion_time' : datetime.fromtimestamp(self.entries[i].deletion_time),
+ 'deferment_end_time' : datetime.fromtimestamp(self.entries[i].deferment_end_time)
+ }
+
+ def __dealloc__(self):
+ rbd_trash_list_cleanup(self.entries, self.num_entries)
+ if self.entries:
+ free(self.entries)
+
from rbd import (RBD, Image, ImageNotFound, InvalidArgument, ImageExists,
ImageBusy, ImageHasSnapshots, ReadOnlyImage,
FunctionNotSupported, ArgumentOutOfRange,
- DiskQuotaExceeded, ConnectionShutdown,
+ DiskQuotaExceeded, ConnectionShutdown, PermissionError,
RBD_FEATURE_LAYERING, RBD_FEATURE_STRIPINGV2,
RBD_FEATURE_EXCLUSIVE_LOCK, RBD_FEATURE_JOURNALING,
RBD_MIRROR_MODE_DISABLED, RBD_MIRROR_MODE_IMAGE,
eq(N + 1, len(images))
for i in range(N):
self.rbd.remove(ioctx, image_name + str(i))
+
+
+class TestTrash(object):
+
+ def setUp(self):
+ global rados2
+ rados2 = Rados(conffile='')
+ rados2.connect()
+ global ioctx2
+ ioctx2 = rados2.open_ioctx(pool_name)
+
+ def tearDown(self):
+ global ioctx2
+ ioctx2.close()
+ global rados2
+ rados2.shutdown()
+
+ def test_move(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 1000)
+ RBD().trash_remove(ioctx, image_id, True)
+
+ def test_remove_denied(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 1000)
+ assert_raises(PermissionError, RBD().trash_remove, ioctx, image_id)
+
+ def test_remove(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+
+ RBD().trash_move(ioctx, image_name, 0)
+ RBD().trash_remove(ioctx, image_id)
+
+ def test_list(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id1 = image.id()
+ image_name1 = image_name
+ RBD().trash_move(ioctx, image_name, 1000)
+
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id2 = image.id()
+ image_name2 = image_name
+ RBD().trash_move(ioctx, image_name, 1000)
+
+ entries = list(RBD().trash_list(ioctx))
+ for e in entries:
+ if e['id'] == image_id1:
+ eq(e['name'], image_name1)
+ elif e['id'] == image_id2:
+ eq(e['name'], image_name2)
+ else:
+ assert False
+ eq(e['source'], 'USER')
+ assert e['deferment_end_time'] > e['deletion_time']
+
+ RBD().trash_remove(ioctx, image_id1, True)
+ RBD().trash_remove(ioctx, image_id2, True)
+
+ def test_restore(self):
+ create_image()
+ with Image(ioctx, image_name) as image:
+ image_id = image.id()
+ RBD().trash_move(ioctx, image_name, 1000)
+ RBD().trash_restore(ioctx, image_id, image_name)
+ remove_image()