Skip to content

Commit

Permalink
python-rados: Add object lock support
Browse files Browse the repository at this point in the history
This change adds to the python binding the support of:
 - rados_lock_exclusive
 - rados_lock_shared
 - rados_unlock

http://tracker.ceph.com/issues/6114 Refs: #6114

Signed-off-by: Mehdi Abaakouk <sileht@redhat.com>
  • Loading branch information
sileht authored and Mehdi Abaakouk committed Dec 6, 2014
1 parent 9029813 commit f5bf75f
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 1 deletion.
131 changes: 130 additions & 1 deletion src/pybind/rados.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -1777,6 +1786,126 @@ def get_last_version(self):
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):

This comment has been minimized.

Copy link
@tchaikov

tchaikov Dec 7, 2014

Contributor

could extract the type checking code into a decorator

from functools import wraps
from itertools import chain


# kwargs is an un-ordered dict, so use args instead
def requires(*types):
    def is_type_of(v, t):
        if t is None:
            return v is None
        else:
            return isinstance(v, t)

    def check_type(val, arg_name, arg_type):
        if isinstance(arg_type, tuple):
            if any(is_type_of(val, t) for t in arg_type):
                return
            type_names = ' or '.join('None' if t is None else t.__name__ 
                                     for t in arg_type)
            raise TypeError('%s must be %s' % (arg_name, type_names))
        else:
            if is_type_of(val, arg_type):
                return
            assert(arg_type is not None)
            raise TypeError('%s must be %s' % (arg_name, arg_type.__name__))

    def wrapper(f):
        @wraps(f)
        def validate_func(*args, **kwargs):
            # ignore the `self` arg
            pos_args = zip(args[1:], types)
            named_args = ((kwargs[name], (name, spec)) for name, spec in types
                          if name in kwargs)
            for arg_val, (arg_name, arg_type) in chain(pos_args, named_args):
                check_type(arg_val, arg_name, arg_type)
            return f(*args, **kwargs)
        return validate_func
    return wrapper


@requires(('key', str), ('name', str), ('cookie', str), ('desc', str), ('duration', (int, None)), ('flags', int))

This comment has been minimized.

Copy link
@tchaikov

tchaikov Dec 7, 2014

Contributor

thanks @dachary , will do.

This comment has been minimized.

Copy link
@tchaikov

tchaikov Dec 7, 2014

Contributor

i pulled together a patch at tchaikov@cf65ba7.

will test it locally before sending a pull request.


"""
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:
Expand Down
23 changes: 23 additions & 0 deletions src/test/pybind/test_rados.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -408,6 +409,28 @@ def cb(_, buf):

[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):
Expand Down

0 comments on commit f5bf75f

Please sign in to comment.