]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
pybind: librbd: added trash API to python RBD binding 13105/head
authorRicardo Dias <rdias@suse.com>
Mon, 27 Mar 2017 14:34:26 +0000 (15:34 +0100)
committerRicardo Dias <rdias@suse.com>
Tue, 11 Apr 2017 11:09:41 +0000 (12:09 +0100)
Signed-off-by: Ricardo Dias <rdias@suse.com>
src/pybind/rbd/rbd.pyx
src/test/pybind/test_rbd.py

index 73ef177db9b2d215b2c96cfe63043b960fd2d7c6..90f855e502e82787ef99757fbb3e0e2944e6e050 100644 (file)
@@ -137,6 +137,17 @@ cdef extern from "rbd/librbd.h" nogil:
         _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)
 
@@ -168,6 +179,14 @@ cdef extern from "rbd/librbd.h" nogil:
     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,
@@ -848,6 +867,81 @@ class RBD(object):
         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.
@@ -2553,3 +2647,56 @@ cdef class SnapIterator(object):
         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)
+
index a0368b64ca6f29da2f61b3942a475bd1ededb0dd..e1961687d74c5713928009adbdad7934b1365b51 100644 (file)
@@ -15,7 +15,7 @@ from rados import (Rados,
 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,
@@ -1428,3 +1428,78 @@ class TestMirroring(object):
         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()