diff --git a/src/pybind/rados.py b/src/pybind/rados.py index 8f48938c574..82521cacb8e 100644 --- a/src/pybind/rados.py +++ b/src/pybind/rados.py @@ -5,7 +5,7 @@ Copyright 2011, Hannu Valtonen """ from ctypes import CDLL, c_char_p, c_size_t, c_void_p, c_char, c_int, c_long, \ c_ulong, create_string_buffer, byref, Structure, c_uint64, c_ubyte, \ - pointer, CFUNCTYPE, c_int64 + pointer, CFUNCTYPE, c_int64, c_uint8 from ctypes.util import find_library import ctypes import errno @@ -41,6 +41,10 @@ class ObjectExists(Error): """ `ObjectExists` class, derived from `Error` """ pass +class ObjectBusy(Error): + """ `ObjectBusy` class, derived from `Error` """ + pass + class IOError(Error): """ `IOError` class, derived from `Error` """ pass @@ -90,6 +94,7 @@ def make_ex(ret, msg): errno.EIO : IOError, errno.ENOSPC : NoSpace, errno.EEXIST : ObjectExists, + errno.EBUSY : ObjectBusy, errno.ENODATA : NoData, errno.EINTR : InterruptedOrTimeoutError, errno.ETIMEDOUT : TimedOut @@ -122,6 +127,10 @@ class rados_cluster_stat_t(Structure): ("kb_avail", c_uint64), ("num_objects", c_uint64)] +class timeval(Structure): + _fields_ = [("tv_sec", c_long), ("tv_usec", c_long)] + + class Version(object): """ Version information """ def __init__(self, major, minor, extra): @@ -1777,6 +1786,126 @@ returned %d, but should return zero on success." % (self.name, ret)) self.require_ioctx_open() return run_in_thread(self.librados.rados_get_last_version, (self.io,)) + def lock_exclusive(self, key, name, cookie, desc="", duration=None, flags=0): + + """ + Take an exclusive lock on an object + + :param key: name of the object + :type key: str + :param name: name of the lock + :type name: str + :param cookie: cookie of the lock + :type cookie: str + :param desc: description of the lock + :type desc: str + :param duration: duration of the lock in seconds + :type duration: int + :param flags: flags + :type flags: int + + :raises: :class:`TypeError` + :raises: :class:`Error` + """ + self.require_ioctx_open() + if not isinstance(key, str): + raise TypeError('key must be a string') + if not isinstance(name, str): + raise TypeError('name must be a string') + if not isinstance(cookie, str): + raise TypeError('cookie must be a string') + if not isinstance(desc, str): + raise TypeError('desc must be a string') + if duration is not None and not isinstance(duration, int): + raise TypeError('duration must be a integer') + if not isinstance(flags, int): + raise TypeError('flags must be a integer') + + ret = run_in_thread(self.librados.rados_lock_exclusive, + (self.io, c_char_p(key), c_char_p(name), c_char_p(cookie), + c_char_p(desc), + timeval(duration, None) if duration is None else None, + c_uint8(flags))) + if ret < 0: + raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key)) + + def lock_shared(self, key, name, cookie, tag, desc="", duration=None, flags=0): + + """ + Take a shared lock on an object + + :param key: name of the object + :type key: str + :param name: name of the lock + :type name: str + :param cookie: cookie of the lock + :type cookie: str + :param tag: tag of the lock + :type tag: str + :param desc: description of the lock + :type desc: str + :param duration: duration of the lock in seconds + :type duration: int + :param flags: flags + :type flags: int + + :raises: :class:`TypeError` + :raises: :class:`Error` + """ + self.require_ioctx_open() + if not isinstance(key, str): + raise TypeError('key must be a string') + if not isinstance(name, str): + raise TypeError('name must be a string') + if not isinstance(cookie, str): + raise TypeError('cookie must be a string') + if not isinstance(tag, str): + raise TypeError('tag must be a string') + if not isinstance(desc, str): + raise TypeError('desc must be a string') + if duration is not None and not isinstance(duration, int): + raise TypeError('duration must be a integer') + if not isinstance(flags, int): + raise TypeError('flags must be a integer') + + ret = run_in_thread(self.librados.rados_lock_shared, + (self.io, c_char_p(key), c_char_p(name), c_char_p(cookie), + c_char_p(tag), c_char_p(desc), + timeval(duration, None) if duration is None else None, + c_uint8(flags))) + if ret < 0: + raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key)) + + def unlock(self, key, name, cookie): + + """ + Release a shared or exclusive lock on an object + + :param key: name of the object + :type key: str + :param name: name of the lock + :type name: str + :param cookie: cookie of the lock + :type cookie: str + + :raises: :class:`TypeError` + :raises: :class:`Error` + """ + self.require_ioctx_open() + if not isinstance(key, str): + raise TypeError('key must be a string') + if not isinstance(name, str): + raise TypeError('name must be a string') + if not isinstance(cookie, str): + raise TypeError('cookie must be a string') + + ret = run_in_thread(self.librados.rados_unlock, + (self.io, c_char_p(key), c_char_p(name), c_char_p(cookie))) + if ret < 0: + raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key)) + + + def set_object_locator(func): def retfunc(self, *args, **kwargs): if self.locator_key is not None: diff --git a/src/test/pybind/test_rados.py b/src/test/pybind/test_rados.py index e96010968c2..758993fbd89 100644 --- a/src/test/pybind/test_rados.py +++ b/src/test/pybind/test_rados.py @@ -1,5 +1,6 @@ from nose.tools import eq_ as eq, assert_raises from rados import (Rados, Error, Object, ObjectExists, ObjectNotFound, + ObjectBusy, ANONYMOUS_AUID, ADMIN_AUID, LIBRADOS_ALL_NSPACES) import time import threading @@ -408,6 +409,28 @@ class TestIoctx(object): [i.remove() for i in self.ioctx.list_objects()] + def test_lock(self): + self.ioctx.lock_exclusive("foo", "lock", "locker", "desc_lock", + 10000, 0) + 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") + + self.ioctx.lock_shared("foo", "lock", "locker1", "tag", "desc_lock", + 10000, 0) + self.ioctx.lock_shared("foo", "lock", "locker2", "tag", "desc_lock", + 10000, 0) + 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") + assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker1") + assert_raises(ObjectNotFound, self.ioctx.unlock, "foo", "lock", "locker2") + + class TestObject(object): def setUp(self):