Skip to content

Commit 456b4d9

Browse files
committed
pybind/rados: Add list_lockers() and break_lock() to Rados Python interface.
Fixes: https://tracker.ceph.com/issues/72544 Signed-off-by: Gil Bregman <[email protected]>
1 parent 804e1b8 commit 456b4d9

File tree

4 files changed

+176
-1
lines changed

4 files changed

+176
-1
lines changed

src/pybind/rados/c_rados.pxd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ cdef extern from "rados/librados.h" nogil:
239239
const char * cookie, const char * tag, const char * desc,
240240
timeval * duration, uint8_t flags)
241241
int rados_unlock(rados_ioctx_t io, const char * o, const char * name, const char * cookie)
242+
int rados_break_lock(rados_ioctx_t io, const char *o, const char *name,
243+
const char *client, const char *cookie)
244+
ssize_t rados_list_lockers(rados_ioctx_t io, const char *o, const char *name,
245+
int *exclusive, char *tag, size_t *tag_len,
246+
char *clients, size_t *clients_len,
247+
char *cookies, size_t *cookies_len,
248+
char *addrs, size_t *addrs_len)
242249

243250
rados_write_op_t rados_create_write_op()
244251
void rados_release_write_op(rados_write_op_t write_op)

src/pybind/rados/mock_rados.pxi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,14 @@ cdef nogil:
331331
pass
332332
int rados_unlock(rados_ioctx_t io, const char * o, const char * name, const char * cookie):
333333
pass
334+
int rados_break_lock(rados_ioctx_t io, const char *o, const char *name,
335+
const char *client, const char *cookie):
336+
pass
337+
ssize_t rados_list_lockers(rados_ioctx_t io, const char *o, const char *name,
338+
int *exclusive, char *tag, size_t *tag_len,
339+
char *clients, size_t *clients_len, char *cookies,
340+
size_t *cookies_len, char *addrs, size_t *addrs_len):
341+
pass
334342

335343
rados_write_op_t rados_create_write_op():
336344
pass

src/pybind/rados/rados.pyx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4009,6 +4009,114 @@ returned %d, but should return zero on success." % (self.name, ret))
40094009
if ret < 0:
40104010
raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key))
40114011

4012+
def break_lock(self, key: str, name: str, client: str, cookie: str):
4013+
4014+
"""
4015+
Release a shared or exclusive lock on an object, which was taken by the
4016+
specified client.
4017+
4018+
:param key: name of the object
4019+
:param name: name of the lock
4020+
:param client: the client currently holding the lock
4021+
:param cookie: cookie of the lock
4022+
4023+
:raises: :class:`TypeError`
4024+
:raises: :class:`Error`
4025+
"""
4026+
self.require_ioctx_open()
4027+
4028+
key_raw = cstr(key, 'key')
4029+
name_raw = cstr(name, 'name')
4030+
client_raw = cstr(client, 'client')
4031+
cookie_raw = cstr(cookie, 'cookie')
4032+
4033+
cdef:
4034+
char* _key = key_raw
4035+
char* _name = name_raw
4036+
char* _client = client_raw
4037+
char* _cookie = cookie_raw
4038+
4039+
with nogil:
4040+
ret = rados_break_lock(self.io, _key, _name, _client, _cookie)
4041+
if ret < 0:
4042+
raise make_ex(ret,
4043+
"Ioctx.rados_break_lock(%s): failed to break lock %s "
4044+
"on %s for client %s "
4045+
"cookie %s" % (self.name, name, key, client, cookie))
4046+
4047+
def list_lockers(self, key: str, name: str):
4048+
4049+
"""
4050+
List clients that have locked the named object lock and
4051+
information about the lock.
4052+
4053+
:param key: name of the object
4054+
:param name: name of the lock
4055+
:returns: dict - contains the following keys:
4056+
* ``tag`` - the tag associated with the lock (every
4057+
additional locker must use the same tag)
4058+
* ``exclusive`` - boolean indicating whether the
4059+
lock is exclusive or shared
4060+
* ``lockers`` - a list of (client, cookie, address)
4061+
tuples
4062+
4063+
:raises: :class:`TypeError`
4064+
:raises: :class:`Error`
4065+
"""
4066+
self.require_ioctx_open()
4067+
4068+
key_raw = cstr(key, 'key')
4069+
name_raw = cstr(name, 'name')
4070+
4071+
cdef:
4072+
char* _key = key_raw
4073+
char* _name = name_raw
4074+
int exclusive = 0
4075+
char* c_tag = NULL
4076+
size_t tag_size = 512
4077+
char* c_clients = NULL
4078+
size_t clients_size = 512
4079+
char* c_cookies = NULL
4080+
size_t cookies_size = 512
4081+
char* c_addrs = NULL
4082+
size_t addrs_size = 512
4083+
4084+
try:
4085+
while True:
4086+
c_tag = <char *>realloc_chk(c_tag, tag_size)
4087+
c_cookies = <char *>realloc_chk(c_cookies, cookies_size)
4088+
c_clients = <char *>realloc_chk(c_clients, clients_size)
4089+
c_addrs = <char *>realloc_chk(c_addrs, addrs_size)
4090+
with nogil:
4091+
ret = rados_list_lockers(self.io, _key, _name, &exclusive,
4092+
c_tag, &tag_size, c_clients, &clients_size,
4093+
c_cookies, &cookies_size, c_addrs, &addrs_size)
4094+
if ret >= 0:
4095+
break
4096+
elif ret != -errno.ERANGE:
4097+
raise make_ex(ret,
4098+
"Ioctx.rados_list_lockers(%s): failed to "
4099+
"list lockers of lock %s on %s" % (self.name, name, key))
4100+
clients = []
4101+
cookies = []
4102+
addrs = []
4103+
4104+
if ret > 0:
4105+
clients = map(decode_cstr, c_clients[:clients_size - 1].split(b'\0'))
4106+
cookies = map(decode_cstr, c_cookies[:cookies_size - 1].split(b'\0'))
4107+
addrs = map(decode_cstr, c_addrs[:addrs_size - 1].split(b'\0'))
4108+
4109+
return {
4110+
'tag' : decode_cstr(c_tag),
4111+
'exclusive' : exclusive == 1,
4112+
'lockers' : list(zip(clients, cookies, addrs)),
4113+
}
4114+
finally:
4115+
free(c_tag)
4116+
free(c_cookies)
4117+
free(c_clients)
4118+
free(c_addrs)
4119+
40124120
def set_osdmap_full_try(self):
40134121
"""
40144122
Set global osdmap_full_try label to true

src/test/pybind/test_rados.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import print_function
22
from assertions import assert_equal as eq, assert_raises
33
from rados import (Rados, Error, RadosStateError, Object, ObjectExists,
4-
ObjectNotFound, ObjectBusy, NotConnected,
4+
ObjectNotFound, ObjectBusy, NotConnected, InvalidArgumentError,
55
LIBRADOS_ALL_NSPACES, WriteOpCtx, ReadOpCtx, LIBRADOS_CREATE_EXCLUSIVE,
66
LIBRADOS_CMPXATTR_OP_EQ, LIBRADOS_CMPXATTR_OP_GT, LIBRADOS_CMPXATTR_OP_LT, OSError,
77
LIBRADOS_SNAP_HEAD, LIBRADOS_OPERATION_BALANCE_READS, LIBRADOS_OPERATION_SKIPRWLOCKS, MonitorLog, MAX_ERRNO, NoData, ExtendMismatch)
@@ -1057,23 +1057,75 @@ def cb(_, buf):
10571057
def test_lock(self):
10581058
self.ioctx.lock_exclusive("foo", "lock", "locker", "desc_lock",
10591059
10000, 0)
1060+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1061+
eq(lockers_info["tag"], "")
1062+
eq(lockers_info["exclusive"], True)
1063+
eq(len(lockers_info["lockers"]), 1)
10601064
assert_raises(ObjectExists,
10611065
self.ioctx.lock_exclusive,
10621066
"foo", "lock", "locker", "desc_lock", 10000, 0)
10631067
self.ioctx.unlock("foo", "lock", "locker")
10641068
assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker")
1069+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1070+
eq(lockers_info["tag"], "")
1071+
eq(lockers_info["exclusive"], True)
1072+
eq(len(lockers_info["lockers"]), 0)
10651073

10661074
self.ioctx.lock_shared("foo", "lock", "locker1", "tag", "desc_lock",
10671075
10000, 0)
10681076
self.ioctx.lock_shared("foo", "lock", "locker2", "tag", "desc_lock",
10691077
10000, 0)
1078+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1079+
eq(lockers_info["tag"], "tag")
1080+
eq(lockers_info["exclusive"], False)
1081+
eq(len(lockers_info["lockers"]), 2)
10701082
assert_raises(ObjectBusy,
10711083
self.ioctx.lock_exclusive,
10721084
"foo", "lock", "locker3", "desc_lock", 10000, 0)
10731085
self.ioctx.unlock("foo", "lock", "locker1")
10741086
self.ioctx.unlock("foo", "lock", "locker2")
1087+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1088+
eq(lockers_info["tag"], "tag")
1089+
eq(lockers_info["exclusive"], False)
1090+
eq(len(lockers_info["lockers"]), 0)
10751091
assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker1")
10761092
assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker2")
1093+
self.ioctx.lock_shared("foo", "lock", "locker3", "tag", "desc_lock",
1094+
10000, 0)
1095+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1096+
eq(lockers_info["tag"], "tag")
1097+
eq(lockers_info["exclusive"], False)
1098+
eq(len(lockers_info["lockers"]), 1)
1099+
lock_client = lockers_info["lockers"][0][0]
1100+
assert_raises(ObjectNotFound,
1101+
self.ioctx.break_lock, "bar", "lock",
1102+
lock_client, "locker3")
1103+
assert_raises(ObjectNotFound,
1104+
self.ioctx.break_lock, "foo", "wrong",
1105+
lock_client, "locker3")
1106+
assert_raises(InvalidArgumentError,
1107+
self.ioctx.break_lock, "foo", "lock",
1108+
"wrong_client", "locker3")
1109+
assert_raises(ObjectNotFound,
1110+
self.ioctx.break_lock, "foo", "lock",
1111+
lock_client, "wrong cookie")
1112+
self.ioctx.lock_shared("foo", "lock", "locker4", "tag", "desc_lock",
1113+
10000, 0)
1114+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1115+
eq(lockers_info["tag"], "tag")
1116+
eq(lockers_info["exclusive"], False)
1117+
eq(len(lockers_info["lockers"]), 2)
1118+
self.ioctx.break_lock("foo", "lock", lock_client, "locker3")
1119+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1120+
eq(lockers_info["tag"], "tag")
1121+
eq(lockers_info["exclusive"], False)
1122+
eq(len(lockers_info["lockers"]), 1)
1123+
assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker3")
1124+
self.ioctx.unlock("foo", "lock", "locker4")
1125+
lockers_info = self.ioctx.list_lockers("foo", "lock")
1126+
eq(lockers_info["tag"], "tag")
1127+
eq(lockers_info["exclusive"], False)
1128+
eq(len(lockers_info["lockers"]), 0)
10771129

10781130
def test_execute(self):
10791131
self.ioctx.write("foo", b"") # ensure object exists

0 commit comments

Comments
 (0)