From 68d49c826686d32e75fd2367a4c59f2609ac0bf0 Mon Sep 17 00:00:00 2001 From: Igor Fedotov Date: Mon, 12 Sep 2022 01:04:17 +0300 Subject: [PATCH] pybind/cephfs: add snapdiff api binding Signed-off-by: Igor Fedotov (cherry picked from commit a67a41c68ab68494a6bab8b084bfc4c5f1a02a0d) Conflicts: src/test/pybind/test_cephfs.py --- src/pybind/cephfs/c_cephfs.pxd | 15 ++++++ src/pybind/cephfs/cephfs.pyx | 88 +++++++++++++++++++++++++++++-- src/pybind/cephfs/mock_cephfs.pxi | 12 +++++ src/test/pybind/test_cephfs.py | 67 ++++++++++++++++++++--- 4 files changed, 172 insertions(+), 10 deletions(-) diff --git a/src/pybind/cephfs/c_cephfs.pxd b/src/pybind/cephfs/c_cephfs.pxd index 4636b4bf45d..69d24912b4c 100644 --- a/src/pybind/cephfs/c_cephfs.pxd +++ b/src/pybind/cephfs/c_cephfs.pxd @@ -36,6 +36,13 @@ cdef extern from "cephfs/libcephfs.h" nogil: size_t nr_snap_metadata snap_metadata *snap_metadata + cdef struct ceph_snapdiff_info: + pass + + cdef struct ceph_snapdiff_entry_t: + dirent dir_entry + uint64_t snapid + ctypedef void* rados_t const char *ceph_version(int *major, int *minor, int *patch) @@ -111,6 +118,14 @@ cdef extern from "cephfs/libcephfs.h" nogil: void ceph_seekdir(ceph_mount_info *cmount, ceph_dir_result *dirp, int64_t offset) int ceph_chdir(ceph_mount_info *cmount, const char *path) dirent * ceph_readdir(ceph_mount_info *cmount, ceph_dir_result *dirp) + int ceph_open_snapdiff(ceph_mount_info *cmount, + const char *root_path, + const char *rel_path, + const char *snap1, + const char *snap2, + ceph_snapdiff_info *out) + int ceph_readdir_snapdiff(ceph_snapdiff_info *snapdiff, ceph_snapdiff_entry_t *out); + int ceph_close_snapdiff(ceph_snapdiff_info *snapdiff) int ceph_rmdir(ceph_mount_info *cmount, const char *path) const char* ceph_getcwd(ceph_mount_info *cmount) int ceph_sync_fs(ceph_mount_info *cmount) diff --git a/src/pybind/cephfs/cephfs.pyx b/src/pybind/cephfs/cephfs.pyx index fca3846de64..793d88b9850 100644 --- a/src/pybind/cephfs/cephfs.pyx +++ b/src/pybind/cephfs/cephfs.pyx @@ -57,6 +57,8 @@ CEPH_SETATTR_SIZE = 0x20 CEPH_SETATTR_CTIME = 0x40 CEPH_SETATTR_BTIME = 0x200 +CEPH_NOSNAP = -2 + # errno definitions cdef enum: CEPHFS_EBLOCKLISTED = 108 @@ -219,7 +221,7 @@ cdef make_ex(ret, msg): class DirEntry(namedtuple('DirEntry', - ['d_ino', 'd_off', 'd_reclen', 'd_type', 'd_name'])): + ['d_ino', 'd_off', 'd_reclen', 'd_type', 'd_name', 'd_snapid'])): DT_DIR = 0x4 DT_REG = 0x8 DT_LNK = 0xA @@ -277,13 +279,15 @@ cdef class DirResult(object): d_off=0, d_reclen=dirent.d_reclen, d_type=dirent.d_type, - d_name=dirent.d_name) + d_name=dirent.d_name, + d_snapid=CEPH_NOSNAP) ELSE: return DirEntry(d_ino=dirent.d_ino, d_off=dirent.d_off, d_reclen=dirent.d_reclen, d_type=dirent.d_type, - d_name=dirent.d_name) + d_name=dirent.d_name, + d_snapid=CEPH_NOSNAP) def close(self): if self.handle: @@ -321,6 +325,56 @@ cdef class DirResult(object): with nogil: ceph_seekdir(self.lib.cluster, self.handle, _offset) +cdef class SnapDiffHandle(object): + cdef LibCephFS lib + cdef ceph_snapdiff_info handle + cdef int opened + + def __cinit__(self, _lib): + self.opened = 0 + self.lib = _lib + + def __dealloc__(self): + self.close() + + def readdir(self): + self.lib.require_state("mounted") + + cdef: + ceph_snapdiff_entry_t difent + with nogil: + ret = ceph_readdir_snapdiff(&self.handle, &difent) + if ret < 0: + raise make_ex(ret, "ceph_readdir_snapdiff failed, ret {}" + .format(ret)) + if ret == 0: + return None + + IF UNAME_SYSNAME == "FreeBSD" or UNAME_SYSNAME == "Darwin": + return DirEntry(d_ino=difent.dir_entry.d_ino, + d_off=0, + d_reclen=difent.dir_entry.d_reclen, + d_type=difent.dir_entry.d_type, + d_name=difent.dir_entry.d_name, + d_snapid=difent.snapid) + ELSE: + return DirEntry(d_ino=difent.dir_entry.d_ino, + d_off=difent.dir_entry.d_off, + d_reclen=difent.dir_entry.d_reclen, + d_type=difent.dir_entry.d_type, + d_name=difent.dir_entry.d_name, + d_snapid=difent.snapid) + + def close(self): + if (not self.opened): + return + self.lib.require_state("mounted") + with nogil: + ret = ceph_close_snapdiff(&self.handle) + if ret < 0: + raise make_ex(ret, "closesnapdiff failed") + self.opened = 0 + def cstr(val, name, encoding="utf-8", opt=False) -> bytes: """ @@ -974,6 +1028,34 @@ cdef class LibCephFS(object): return handle.close() + def opensnapdiff(self, root_path, rel_path, snap1name, snap2name) -> SnapDiffHandle: + """ + Open the given directory. + + :param path: the path name of the directory to open. Must be either an absolute path + or a path relative to the current working directory. + :returns: the open directory stream handle + """ + self.require_state("mounted") + + h = SnapDiffHandle(self) + root = cstr(root_path, 'root') + relp = cstr(rel_path, 'relp') + snap1 = cstr(snap1name, 'snap1') + snap2 = cstr(snap2name, 'snap2') + cdef: + char* _root = root + char* _relp = relp + char* _snap1 = snap1 + char* _snap2 = snap2 + with nogil: + ret = ceph_open_snapdiff(self.cluster, _root, _relp, _snap1, _snap2, &h.handle); + if ret < 0: + raise make_ex(ret, "open_snapdiff failed for {} vs. {}" + .format(snap1.decode('utf-8'), snap2.decode('utf-8'))) + h.opened = 1 + return h + def rewinddir(self, DirResult handle): """ Rewind the directory stream to the beginning of the directory. diff --git a/src/pybind/cephfs/mock_cephfs.pxi b/src/pybind/cephfs/mock_cephfs.pxi index 1dec0d50d54..54b27d04c67 100644 --- a/src/pybind/cephfs/mock_cephfs.pxi +++ b/src/pybind/cephfs/mock_cephfs.pxi @@ -39,6 +39,12 @@ cdef nogil: size_t nr_snap_metadata snap_metadata *snap_metadata + cdef struct ceph_snapdiff_info: + int dummy + + cdef struct ceph_snapdiff_entry_t: + int dummy + ctypedef void* rados_t const char *ceph_version(int *major, int *minor, int *patch): @@ -175,6 +181,12 @@ cdef nogil: pass dirent * ceph_readdir(ceph_mount_info *cmount, ceph_dir_result *dirp): pass + int ceph_open_snapdiff(ceph_mount_info *cmount, const char *root_path, const char *rel_path, const char *snap1path, const char *snap2root, ceph_snapdiff_info *out): + pass + int ceph_readdir_snapdiff(ceph_snapdiff_info *snapdiff, ceph_snapdiff_entry_t *out): + pass + int ceph_close_snapdiff(ceph_snapdiff_info *snapdiff): + pass int ceph_rmdir(ceph_mount_info *cmount, const char *path): pass const char* ceph_getcwd(ceph_mount_info *cmount): diff --git a/src/test/pybind/test_cephfs.py b/src/test/pybind/test_cephfs.py index 0991730706d..d16807de97d 100644 --- a/src/test/pybind/test_cephfs.py +++ b/src/test/pybind/test_cephfs.py @@ -1,5 +1,7 @@ # vim: expandtab smarttab shiftwidth=4 softtabstop=4 from assertions import assert_raises, assert_equal, assert_not_equal, assert_greater +import collections +collections.Callable = collections.abc.Callable import cephfs as libcephfs import fcntl import os @@ -21,21 +23,37 @@ def teardown_module(): global cephfs cephfs.shutdown() -@pytest.fixture -def testdir(): - d = cephfs.opendir(b"/") +def purge_dir(path, is_snap = False): + print(b"Purge " + path) + d = cephfs.opendir(path) + if (not path.endswith(b"/")): + path = path + b"/" dent = cephfs.readdir(d) while dent: if (dent.d_name not in [b".", b".."]): + print(path + dent.d_name) if dent.is_dir(): - cephfs.rmdir(b"/" + dent.d_name) + if (not is_snap): + try: + snappath = path + dent.d_name + b"/.snap" + cephfs.stat(snappath) + purge_dir(snappath, True) + except: + pass + purge_dir(path + dent.d_name, False) + cephfs.rmdir(path + dent.d_name) + else: + print("rmsnap on {} snap {}".format(path, dent.d_name)) + cephfs.rmsnap(path, dent.d_name); else: - cephfs.unlink(b"/" + dent.d_name) - + cephfs.unlink(path + dent.d_name) dent = cephfs.readdir(d) - cephfs.closedir(d) +@pytest.fixture +def testdir(): + purge_dir(b"/") + cephfs.chdir(b"/") _, ret_buf = cephfs.listxattr("/") print(f'ret_buf={ret_buf}') @@ -854,3 +872,38 @@ def test_set_mount_timeout_lt0(testdir): cephfs.unmount() assert_raises(libcephfs.InvalidValue, cephfs.set_mount_timeout, -5) cephfs.mount() + +def test_snapdiff(testdir): + cephfs.mkdir("/snapdiff_test", 0o755) + fd = cephfs.open('/snapdiff_test/file-1', 'w', 0o755) + cephfs.write(fd, b"1111", 0) + cephfs.close(fd) + fd = cephfs.open('/snapdiff_test/file-2', 'w', 0o755) + cephfs.write(fd, b"2222", 0) + cephfs.close(fd) + cephfs.mksnap("/snapdiff_test", "snap1", 0o755) + fd = cephfs.open('/snapdiff_test/file-1', 'w', 0o755) + cephfs.write(fd, b"1222", 0) + cephfs.close(fd) + cephfs.unlink('/snapdiff_test/file-2') + cephfs.mksnap("/snapdiff_test", "snap2", 0o755) + snap1id = cephfs.snap_info(b"/snapdiff_test/.snap/snap1")['id'] + snap2id = cephfs.snap_info(b"/snapdiff_test/.snap/snap2")['id'] + diff = cephfs.opensnapdiff(b"/snapdiff_test", b"/", b"snap2", b"snap1") + cnt = 0 + e = diff.readdir() + while e is not None: + if (e.d_name == b"file-1"): + cnt = cnt + 1 + assert_equal(snap2id, e.d_snapid) + elif (e.d_name == b"file-2"): + cnt = cnt + 1 + assert_equal(snap1id, e.d_snapid) + elif (e.d_name != b"." and e.d_name != b".."): + cnt = cnt + 1 + e = diff.readdir() + assert_equal(cnt, 2) + diff.close() + + # remove directory + purge_dir(b"/snapdiff_test"); -- 2.39.5