]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
pybind/rados: Add list_lockers() and break_lock() to Rados Python interface. 65022/head
authorGil Bregman <gbregman@il.ibm.com>
Thu, 14 Aug 2025 05:55:49 +0000 (08:55 +0300)
committerGil Bregman <gbregman@il.ibm.com>
Thu, 14 Aug 2025 05:55:49 +0000 (08:55 +0300)
Fixes: https://tracker.ceph.com/issues/72544
Signed-off-by: Gil Bregman <gbregman@il.ibm.com>
src/pybind/rados/c_rados.pxd
src/pybind/rados/mock_rados.pxi
src/pybind/rados/rados.pyx
src/test/pybind/test_rados.py

index 563f3ec6afbb62d280e15b219716612203d3f454..d4e245142065f4a9012641b1f7016b6805305fba 100644 (file)
@@ -239,6 +239,13 @@ cdef extern from "rados/librados.h" nogil:
                           const char * cookie, const char * tag, const char * desc,
                           timeval * duration, uint8_t flags)
     int rados_unlock(rados_ioctx_t io, const char * o, const char * name, const char * cookie)
+    int rados_break_lock(rados_ioctx_t io, const char *o, const char *name,
+                         const char *client, const char *cookie)
+    ssize_t rados_list_lockers(rados_ioctx_t io, const char *o, const char *name,
+                               int *exclusive, char *tag, size_t *tag_len,
+                               char *clients, size_t *clients_len,
+                               char *cookies, size_t *cookies_len,
+                               char *addrs, size_t *addrs_len)
 
     rados_write_op_t rados_create_write_op()
     void rados_release_write_op(rados_write_op_t write_op)
index 36f5aad7d0a6f69d94c008519039e843bf014eb6..ad4a161c2a461cbe087022b726946408cb2b78c2 100644 (file)
@@ -331,6 +331,14 @@ cdef nogil:
         pass
     int rados_unlock(rados_ioctx_t io, const char * o, const char * name, const char * cookie):
         pass
+    int rados_break_lock(rados_ioctx_t io, const char *o, const char *name,
+                         const char *client, const char *cookie):
+        pass
+    ssize_t rados_list_lockers(rados_ioctx_t io, const char *o, const char *name,
+                               int *exclusive, char *tag, size_t *tag_len,
+                               char *clients, size_t *clients_len, char *cookies,
+                               size_t *cookies_len, char *addrs, size_t *addrs_len):
+        pass
 
     rados_write_op_t rados_create_write_op():
         pass
index bcfa6777f3de190b4b98cad3ee5aee610a779d57..27fa56e5f28793cceb8c05c92ab232e88b5beaa9 100644 (file)
@@ -4009,6 +4009,114 @@ returned %d, but should return zero on success." % (self.name, ret))
         if ret < 0:
             raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key))
 
+    def break_lock(self, key: str, name: str, client: str, cookie: str):
+
+        """
+        Release a shared or exclusive lock on an object, which was taken by the
+        specified client.
+
+        :param key: name of the object
+        :param name: name of the lock
+        :param client: the client currently holding the lock
+        :param cookie: cookie of the lock
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+
+        key_raw = cstr(key, 'key')
+        name_raw = cstr(name, 'name')
+        client_raw = cstr(client, 'client')
+        cookie_raw = cstr(cookie, 'cookie')
+
+        cdef:
+            char* _key = key_raw
+            char* _name = name_raw
+            char* _client = client_raw
+            char* _cookie = cookie_raw
+
+        with nogil:
+            ret = rados_break_lock(self.io, _key, _name, _client, _cookie)
+        if ret < 0:
+            raise make_ex(ret,
+                          "Ioctx.rados_break_lock(%s): failed to break lock %s "
+                          "on %s for client %s "
+                          "cookie %s" % (self.name, name, key, client, cookie))
+
+    def list_lockers(self, key: str, name: str):
+
+        """
+        List clients that have locked the named object lock and
+        information about the lock.
+
+        :param key: name of the object
+        :param name: name of the lock
+        :returns: dict - contains the following keys:
+                  * ``tag`` - the tag associated with the lock (every
+                    additional locker must use the same tag)
+                  * ``exclusive`` - boolean indicating whether the
+                     lock is exclusive or shared
+                  * ``lockers`` - a list of (client, cookie, address)
+                    tuples
+
+        :raises: :class:`TypeError`
+        :raises: :class:`Error`
+        """
+        self.require_ioctx_open()
+
+        key_raw = cstr(key, 'key')
+        name_raw = cstr(name, 'name')
+
+        cdef:
+            char* _key = key_raw
+            char* _name = name_raw
+            int exclusive = 0
+            char* c_tag = NULL
+            size_t tag_size = 512
+            char* c_clients = NULL
+            size_t clients_size = 512
+            char* c_cookies = NULL
+            size_t cookies_size = 512
+            char* c_addrs = NULL
+            size_t addrs_size = 512
+
+        try:
+            while True:
+                c_tag = <char *>realloc_chk(c_tag, tag_size)
+                c_cookies = <char *>realloc_chk(c_cookies, cookies_size)
+                c_clients = <char *>realloc_chk(c_clients, clients_size)
+                c_addrs = <char *>realloc_chk(c_addrs, addrs_size)
+                with nogil:
+                    ret = rados_list_lockers(self.io, _key, _name, &exclusive,
+                                             c_tag, &tag_size, c_clients, &clients_size,
+                                             c_cookies, &cookies_size, c_addrs, &addrs_size)
+                if ret >= 0:
+                    break
+                elif ret != -errno.ERANGE:
+                    raise make_ex(ret,
+                                  "Ioctx.rados_list_lockers(%s): failed to "
+                                  "list lockers of lock %s on %s" % (self.name, name, key))
+            clients = []
+            cookies = []
+            addrs = []
+
+            if ret > 0:
+                clients = map(decode_cstr, c_clients[:clients_size - 1].split(b'\0'))
+                cookies = map(decode_cstr, c_cookies[:cookies_size - 1].split(b'\0'))
+                addrs = map(decode_cstr, c_addrs[:addrs_size - 1].split(b'\0'))
+
+            return {
+                'tag'       : decode_cstr(c_tag),
+                'exclusive' : exclusive == 1,
+                'lockers'   : list(zip(clients, cookies, addrs)),
+                }
+        finally:
+            free(c_tag)
+            free(c_cookies)
+            free(c_clients)
+            free(c_addrs)
+
     def set_osdmap_full_try(self):
         """
         Set global osdmap_full_try label to true
index 881b29c9152219b55d6776cfc6fb5a5afd6ed007..cd8a955dfd4a140491fdfe88cc704ccdf68878e5 100644 (file)
@@ -1,7 +1,7 @@
 from __future__ import print_function
 from assertions import assert_equal as eq, assert_raises
 from rados import (Rados, Error, RadosStateError, Object, ObjectExists,
-                   ObjectNotFound, ObjectBusy, NotConnected,
+                   ObjectNotFound, ObjectBusy, NotConnected, InvalidArgumentError,
                    LIBRADOS_ALL_NSPACES, WriteOpCtx, ReadOpCtx, LIBRADOS_CREATE_EXCLUSIVE,
                    LIBRADOS_CMPXATTR_OP_EQ, LIBRADOS_CMPXATTR_OP_GT, LIBRADOS_CMPXATTR_OP_LT, OSError,
                    LIBRADOS_SNAP_HEAD, LIBRADOS_OPERATION_BALANCE_READS, LIBRADOS_OPERATION_SKIPRWLOCKS, MonitorLog, MAX_ERRNO, NoData, ExtendMismatch)
@@ -1057,23 +1057,75 @@ class TestIoctx(object):
     def test_lock(self):
         self.ioctx.lock_exclusive("foo", "lock", "locker", "desc_lock",
                                   10000, 0)
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "")
+        eq(lockers_info["exclusive"], True)
+        eq(len(lockers_info["lockers"]), 1)
         assert_raises(ObjectExists,
                       self.ioctx.lock_exclusive,
                       "foo", "lock", "locker", "desc_lock", 10000, 0)
         self.ioctx.unlock("foo", "lock", "locker")
         assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker")
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "")
+        eq(lockers_info["exclusive"], True)
+        eq(len(lockers_info["lockers"]), 0)
 
         self.ioctx.lock_shared("foo", "lock", "locker1", "tag", "desc_lock",
                                10000, 0)
         self.ioctx.lock_shared("foo", "lock", "locker2", "tag", "desc_lock",
                                10000, 0)
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "tag")
+        eq(lockers_info["exclusive"], False)
+        eq(len(lockers_info["lockers"]), 2)
         assert_raises(ObjectBusy,
                       self.ioctx.lock_exclusive,
                       "foo", "lock", "locker3", "desc_lock", 10000, 0)
         self.ioctx.unlock("foo", "lock", "locker1")
         self.ioctx.unlock("foo", "lock", "locker2")
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "tag")
+        eq(lockers_info["exclusive"], False)
+        eq(len(lockers_info["lockers"]), 0)
         assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker1")
         assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker2")
+        self.ioctx.lock_shared("foo", "lock", "locker3", "tag", "desc_lock",
+                               10000, 0)
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "tag")
+        eq(lockers_info["exclusive"], False)
+        eq(len(lockers_info["lockers"]), 1)
+        lock_client = lockers_info["lockers"][0][0]
+        assert_raises(ObjectNotFound,
+                      self.ioctx.break_lock, "bar", "lock",
+                      lock_client, "locker3")
+        assert_raises(ObjectNotFound,
+                      self.ioctx.break_lock, "foo", "wrong",
+                      lock_client, "locker3")
+        assert_raises(InvalidArgumentError,
+                      self.ioctx.break_lock, "foo", "lock",
+                      "wrong_client", "locker3")
+        assert_raises(ObjectNotFound,
+                      self.ioctx.break_lock, "foo", "lock",
+                      lock_client, "wrong cookie")
+        self.ioctx.lock_shared("foo", "lock", "locker4", "tag", "desc_lock",
+                               10000, 0)
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "tag")
+        eq(lockers_info["exclusive"], False)
+        eq(len(lockers_info["lockers"]), 2)
+        self.ioctx.break_lock("foo", "lock", lock_client, "locker3")
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "tag")
+        eq(lockers_info["exclusive"], False)
+        eq(len(lockers_info["lockers"]), 1)
+        assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker3")
+        self.ioctx.unlock("foo", "lock", "locker4")
+        lockers_info = self.ioctx.list_lockers("foo", "lock")
+        eq(lockers_info["tag"], "tag")
+        eq(lockers_info["exclusive"], False)
+        eq(len(lockers_info["lockers"]), 0)
 
     def test_execute(self):
         self.ioctx.write("foo", b"") # ensure object exists