From 2f8de8943ef42b3b6f6d87fe6c70cacced0c5cd3 Mon Sep 17 00:00:00 2001 From: Yehuda Sadeh Date: Fri, 20 Jul 2012 12:59:07 -0700 Subject: [PATCH] cls_lock: objclass for advisory locking Providing an objclass to create and manipulate advisory locking. Also providing a client api to control it. A lock may either be exclusively locked or shared among multiple lockers. A locker is identified by the rados client name, and by a cookie-string. A lock may be assigned with a tag that every operation on that lock should use. A lock can be unlocked by the client that locked it, or may be broken by other clients. When a non-zero lock duration is assigned to a lock by a locker, that locker expires after that time duration. A lock may have a description. Locks on a specific object can be listed. Lockers of a specific lock can be enumerated (by get_info). Signed-off-by: Yehuda Sadeh --- src/Makefile.am | 29 +- src/cls/lock/cls_lock.cc | 456 ++++++++++++++++++++++++++++++++ src/cls/lock/cls_lock_client.cc | 209 +++++++++++++++ src/cls/lock/cls_lock_client.h | 88 ++++++ src/cls/lock/cls_lock_ops.cc | 172 ++++++++++++ src/cls/lock/cls_lock_ops.h | 173 ++++++++++++ src/cls/lock/cls_lock_types.cc | 70 +++++ src/cls/lock/cls_lock_types.h | 91 +++++++ src/test/encoding/types.h | 12 + src/test/rados-api/cls_lock.cc | 291 ++++++++++++++++++++ 10 files changed, 1589 insertions(+), 2 deletions(-) create mode 100644 src/cls/lock/cls_lock.cc create mode 100644 src/cls/lock/cls_lock_client.cc create mode 100644 src/cls/lock/cls_lock_client.h create mode 100644 src/cls/lock/cls_lock_ops.cc create mode 100644 src/cls/lock/cls_lock_ops.h create mode 100644 src/cls/lock/cls_lock_types.cc create mode 100644 src/cls/lock/cls_lock_types.h create mode 100644 src/test/rados-api/cls_lock.cc diff --git a/src/Makefile.am b/src/Makefile.am index 8702f3cc6d42f..609eb4fa77af1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -123,7 +123,7 @@ rgw_dencoder_src = rgw/rgw_dencoder.cc \ ceph_dencoder_SOURCES = test/encoding/ceph_dencoder.cc ${rgw_dencoder_src} ceph_dencoder_CXXFLAGS = ${CRYPTO_CXXFLAGS} ${AM_CXXFLAGS} -ceph_dencoder_LDADD = $(LIBGLOBAL_LDA) libosd.a libmds.a $(LIBOS_LDA) libmon.a +ceph_dencoder_LDADD = $(LIBGLOBAL_LDA) libcls_lock_client.a libosd.a libmds.a $(LIBOS_LDA) libmon.a bin_PROGRAMS += ceph-dencoder mount_ceph_SOURCES = mount/mount.ceph.c common/armor.c common/safe_io.c common/secret.c include/addr_parsing.c @@ -361,7 +361,7 @@ librbd_la_LDFLAGS = ${AM_LDFLAGS} -version-info 1:0:0 \ lib_LTLIBRARIES += librbd.la rados_SOURCES = rados.cc rados_import.cc rados_export.cc rados_sync.cc common/obj_bencher.cc -rados_LDADD = libglobal.la librados.la $(PTHREAD_LIBS) -lm $(CRYPTO_LIBS) $(EXTRALIBS) +rados_LDADD = libglobal.la libcls_lock_client.a librados.la $(PTHREAD_LIBS) -lm $(CRYPTO_LIBS) $(EXTRALIBS) bin_PROGRAMS += rados if WITH_REST_BENCH @@ -427,6 +427,16 @@ radoslibdir = $(libdir)/rados-classes radoslib_LTLIBRARIES = libcls_rbd.la +# rbd: rados block device class +libcls_lock_la_SOURCES = cls/lock/cls_lock.cc +libcls_lock_la_CFLAGS = ${AM_CFLAGS} +libcls_lock_la_CXXFLAGS= ${AM_CXXFLAGS} +libcls_lock_la_LIBADD = $(PTHREAD_LIBS) $(EXTRALIBS) +libcls_lock_la_LDFLAGS = ${AM_LDFLAGS} -version-info 1:0:0 -export-symbols-regex '.*__cls_.*' + +radoslib_LTLIBRARIES += libcls_lock.la + + if WITH_RADOSGW # rgw: rados gateway libcls_rgw_la_SOURCES = cls_rgw.cc @@ -438,6 +448,12 @@ libcls_rgw_la_LDFLAGS = ${AM_LDFLAGS} -version-info 1:0:0 -export-symbols-regex radoslib_LTLIBRARIES += libcls_rgw.la endif +libcls_lock_client_a_SOURCES = \ + cls/lock/cls_lock_client.cc \ + cls/lock/cls_lock_types.cc \ + cls/lock/cls_lock_ops.cc +noinst_LIBRARIES += libcls_lock_client.a + ## hadoop client if WITH_HADOOPCLIENT JAVA_BASE = /usr/lib/jvm/java-6-sun @@ -739,6 +755,12 @@ test_rados_api_stat_LDADD = librados.la ${UNITTEST_STATIC_LDADD} test_rados_api_stat_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} bin_DEBUGPROGRAMS += test_rados_api_stat +test_rados_api_cls_lock_SOURCES = test/rados-api/cls_lock.cc test/rados-api/test.cc +test_rados_api_cls_lock_LDFLAGS = ${AM_LDFLAGS} +test_rados_api_cls_lock_LDADD = libcls_lock_client.a librados.la ${UNITTEST_STATIC_LDADD} +test_rados_api_cls_lock_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} +bin_DEBUGPROGRAMS += test_rados_api_cls_lock + test_rados_api_watch_notify_SOURCES = test/rados-api/watch_notify.cc test/rados-api/test.cc test_rados_api_watch_notify_LDFLAGS = ${AM_LDFLAGS} test_rados_api_watch_notify_LDADD = librados.la ${UNITTEST_STATIC_LDADD} @@ -1231,6 +1253,9 @@ noinst_HEADERS = \ client/ObjecterWriteback.h\ cls_acl.cc\ cls_crypto.cc\ + cls/lock/cls_lock_types.h\ + cls/lock/cls_lock_ops.h\ + cls/lock/cls_lock_client.h\ common/BackTrace.h\ common/RefCountedObj.h\ common/HeartbeatMap.h\ diff --git a/src/cls/lock/cls_lock.cc b/src/cls/lock/cls_lock.cc new file mode 100644 index 0000000000000..43915a4663dc2 --- /dev/null +++ b/src/cls/lock/cls_lock.cc @@ -0,0 +1,456 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +/** \file + * + * This is an OSD class that implements methods for object + * advisory locking. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/types.h" +#include "include/utime.h" +#include "objclass/objclass.h" + +#include "common/Clock.h" + +#include "cls/lock/cls_lock_types.h" +#include "cls/lock/cls_lock_ops.h" + +#include "global/global_context.h" + + +CLS_VER(1,0) +CLS_NAME(lock) + +cls_handle_t h_class; +cls_method_handle_t h_lock_op; +cls_method_handle_t h_unlock_op; +cls_method_handle_t h_break_lock; +cls_method_handle_t h_get_info; +cls_method_handle_t h_list_locks; + +#define LOCK_PREFIX "lock." + +typedef struct lock_info_s { + map lockers; + ClsLockType lock_type; + string tag; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(lockers, bl); + uint8_t t = (uint8_t)lock_type; + ::encode(t, bl); + ::encode(tag, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(lockers, bl); + uint8_t t; + ::decode(t, bl); + lock_type = (ClsLockType)t; + ::decode(tag, bl); + DECODE_FINISH(bl); + } + lock_info_s() : lock_type(LOCK_NONE) {} +} lock_info_t; +WRITE_CLASS_ENCODER(lock_info_t) + + +static int read_lock(cls_method_context_t hctx, const string& name, lock_info_t *lock) +{ + bufferlist bl; + string key = LOCK_PREFIX; + key.append(name); + + int r = cls_cxx_getxattr(hctx, key.c_str(), &bl); + if (r < 0) { + if (r == -ENODATA) { + *lock = lock_info_t(); + return 0; + } + if (r != -ENOENT) { + CLS_ERR("error reading xattr %s: %d", key.c_str(), r); + } + return r; + } + + try { + bufferlist::iterator it = bl.begin(); + ::decode(*lock, it); + } catch (const buffer::error &err) { + CLS_ERR("error decoding %s", key.c_str()); + return -EIO; + } + + /* now trim expired locks */ + + utime_t now = ceph_clock_now(g_ceph_context); + + map::iterator iter = lock->lockers.begin(); + + while (iter != lock->lockers.end()) { + map::iterator next = iter; + ++next; + + struct cls_lock_locker_info_t& info = iter->second; + if (!info.duration.is_zero() && info.duration < now) { + CLS_LOG(20, "expiring locker"); + lock->lockers.erase(iter); + } + + iter = next; + } + + return 0; +} + +static int write_lock(cls_method_context_t hctx, const string& name, const lock_info_t& lock) +{ + string key = LOCK_PREFIX; + key.append(name); + + bufferlist lock_bl; + ::encode(lock, lock_bl); + + int r = cls_cxx_setxattr(hctx, key.c_str(), &lock_bl); + if (r < 0) + return r; + + return 0; +} + +/** + * helper function to add a lock and update disk state. + * + * Input: + * @param name Lock name + * @param lock_type Type of lock (exclusive / shared) + * @param duration Type of lock (exclusive / shared) + * @param flags lock flags + * @param cookie The cookie to set in the lock + * @param tag The tag to match with the lock (can only lock with matching tags) + * @param lock_description The lock description to set (if not empty) + * @param locker_description The locker description + * + * @return 0 on success, or -errno on failure + */ +static int lock_obj(cls_method_context_t hctx, + const string& name, + ClsLockType lock_type, + utime_t duration, + const string& description, + uint8_t flags, + const string& cookie, + const string& tag) +{ + bool exclusive = lock_type == LOCK_EXCLUSIVE; + lock_info_t linfo; + bool fail_if_exists = (flags & LOCK_FLAG_RENEW) == 0; + + CLS_LOG(20, "requested lock_type=%s fail_if_exists=%d", cls_lock_type_str(lock_type), fail_if_exists); + if (lock_type != LOCK_EXCLUSIVE && + lock_type != LOCK_SHARED) + return -EINVAL; + + if (name.empty()) + return -EINVAL; + + // see if there's already a locker + int r = read_lock(hctx, name, &linfo); + if (r < 0 && r != -ENOENT) { + CLS_ERR("Could not read lock info: %s", strerror(r)); + return r; + } + map& lockers = linfo.lockers; + map::iterator iter; + + cls_lock_id_t id; + id.cookie = cookie; + entity_inst_t inst; + r = cls_get_request_origin(hctx, &inst); + id.locker = inst.name; + assert(r == 0); + + /* check this early, before we check fail_if_exists, otherwise we might + * remove the locker entry and not check it later */ + if (lockers.size() && tag != linfo.tag) { + CLS_LOG(20, "cannot take lock on object, conflicting tag"); + return -EBUSY; + } + + ClsLockType existing_lock_type = linfo.lock_type; + CLS_LOG(20, "existing_lock_type=%s", cls_lock_type_str(existing_lock_type)); + iter = lockers.find(id); + if (iter != lockers.end()) { + if (fail_if_exists) { + return -EEXIST; + } else { + lockers.erase(iter); // remove old entry + } + } + + if (lockers.size()) { + if (exclusive) { + CLS_LOG(20, "could not exclusive-lock object, already locked"); + return -EBUSY; + } + + if (existing_lock_type != lock_type) { + CLS_LOG(20, "cannot take lock on object, conflicting lock type"); + return -EBUSY; + } + } + + linfo.lock_type = lock_type; + linfo.tag = tag; + utime_t duration_abs; + if (!duration.is_zero()) { + duration_abs = ceph_clock_now(g_ceph_context); + duration_abs += duration; + + } + struct cls_lock_locker_info_t info(duration_abs, inst.addr, description); + + linfo.lockers[id] = info; + + r = write_lock(hctx, name, linfo); + if (r < 0) + return r; + + return 0; +} + +/** + * Set an exclusive lock on an object for the activating client, if possible. + * + * Input: + * @param cls_lock_lock_op request input + * + * @returns 0 on success, -EINVAL if it can't decode the lock_cookie, + * -EBUSY if the object is already locked, or -errno on (unexpected) failure. + */ +static int lock_op(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "lock_op"); + cls_lock_lock_op op; + try { + bufferlist::iterator iter = in->begin(); + ::decode(op, iter); + } catch (const buffer::error &err) { + return -EINVAL; + } + + return lock_obj(hctx, + op.name, op.type, op.duration, op.description, + op.flags, op.cookie, op.tag); +} + +/** + * helper function to remove a lock from on disk and clean up state. + * + * @param name The lock name + * @param locker The locker entity name + * @param cookie The user-defined cookie associated with the lock. + * + * @return 0 on success, -ENOENT if there is no such lock (either + * entity or cookie is wrong), or -errno on other error. + */ +static int remove_lock(cls_method_context_t hctx, + const string& name, + entity_name_t& locker, + const string& cookie) +{ + // get current lockers + lock_info_t linfo; + int r = read_lock(hctx, name, &linfo); + if (r < 0) { + CLS_ERR("Could not read list of current lockers off disk: %s", strerror(r)); + return r; + } + + map& lockers = linfo.lockers; + struct cls_lock_id_t id(locker, cookie); + + // remove named locker from set + map::iterator iter = lockers.find(id); + if (iter == lockers.end()) { // no such key + return -ENOENT; + } + lockers.erase(iter); + + r = write_lock(hctx, name, linfo); + + return r; +} + +/** + * Unlock an object which the activating client currently has locked. + * + * Input: + * @param cls_lock_unlock_op request input + * + * @return 0 on success, -EINVAL if it can't decode the cookie, -ENOENT + * if there is no such lock (either entity or cookie is wrong), or + * -errno on other (unexpected) error. + */ +static int unlock_op(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "unlock_op"); + cls_lock_unlock_op op; + try { + bufferlist::iterator iter = in->begin(); + ::decode(op, iter); + } catch (const buffer::error& err) { + return -EINVAL; + } + + entity_inst_t inst; + int r = cls_get_request_origin(hctx, &inst); + assert(r == 0); + return remove_lock(hctx, op.name, inst.name, op.cookie); +} + +/** + * Break the lock on an object held by any client. + * + * Input: + * @param cls_lock_break_op request input + * + * @return 0 on success, -EINVAL if it can't decode the locker and + * cookie, -ENOENT if there is no such lock (either entity or cookie + * is wrong), or -errno on other (unexpected) error. + */ +static int break_lock(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "break_lock"); + cls_lock_break_op op; + try { + bufferlist::iterator iter = in->begin(); + ::decode(op, iter); + } catch (const buffer::error& err) { + return -EINVAL; + } + + return remove_lock(hctx, op.name, op.locker, op.cookie); +} + + + /** + * Retrieve lock info: lockers, tag, exclusive + * + * Input: + * @param cls_lock_list_lockers_op request input + * + * Output: + * @param cls_lock_list_lockers_reply result + * + * @return 0 on success, -errno on failure. + */ +static int get_info(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "get_info"); + cls_lock_get_info_op op; + try { + bufferlist::iterator iter = in->begin(); + ::decode(op, iter); + } catch (const buffer::error& err) { + return -EINVAL; + } + + // get current lockers + lock_info_t linfo; + int r = read_lock(hctx, op.name, &linfo); + if (r < 0) { + CLS_ERR("Could not read lock info: %s", strerror(r)); + return r; + } + + struct cls_lock_get_info_reply ret; + + map::iterator iter; + for (iter = linfo.lockers.begin(); iter != linfo.lockers.end(); ++iter) { + ret.lockers[iter->first] = iter->second; + } + ret.lock_type = linfo.lock_type; + ret.tag = linfo.tag; + + ::encode(ret, *out); + + return 0; +} + + + /** + * Retrieve a list of locks for this object + * + * Input: + * @param in is ignored. + * + * Output: + * @param + * + * @return 0 on success, -errno on failure. + */static int list_locks(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "list_locks"); + + map attrs; + + int r = cls_cxx_getxattrs(hctx, &attrs); + if (r < 0) + return r; + + cls_lock_list_locks_reply ret; + + map::iterator iter; + size_t pos = sizeof(LOCK_PREFIX) - 1; + for (iter = attrs.begin(); iter != attrs.end(); ++iter) { + const string& attr = iter->first; + if (attr.substr(0, pos).compare(LOCK_PREFIX) == 0) { + ret.locks.push_back(attr.substr(pos)); + } + } + + ::encode(ret, *out); + + return 0; +} + +void __cls_init() +{ + CLS_LOG(20, "Loaded lock class!"); + + cls_register("lock", &h_class); + cls_register_cxx_method(h_class, "lock", + CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC, + lock_op, &h_lock_op); + cls_register_cxx_method(h_class, "unlock", + CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC, + unlock_op, &h_unlock_op); + cls_register_cxx_method(h_class, "break_lock", + CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC, + break_lock, &h_break_lock); + cls_register_cxx_method(h_class, "get_info", + CLS_METHOD_RD | CLS_METHOD_PUBLIC, + get_info, &h_get_info); + cls_register_cxx_method(h_class, "list_locks", + CLS_METHOD_RD | CLS_METHOD_PUBLIC, + list_locks, &h_list_locks); + + return; +} diff --git a/src/cls/lock/cls_lock_client.cc b/src/cls/lock/cls_lock_client.cc new file mode 100644 index 0000000000000..f3b600cded8d7 --- /dev/null +++ b/src/cls/lock/cls_lock_client.cc @@ -0,0 +1,209 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/types.h" +#include "msg/msg_types.h" +#include "include/rados/librados.hpp" + +using namespace librados; + +#include + +#include +#include +#include + +#include "cls/lock/cls_lock_types.h" +#include "cls/lock/cls_lock_ops.h" +#include "cls/lock/cls_lock_client.h" + +namespace rados { + namespace cls { + namespace lock { + + void lock(ObjectWriteOperation& rados_op, + string& name, ClsLockType type, + string& cookie, string& tag, + string description, + utime_t& duration, uint8_t flags) + { + cls_lock_lock_op op; + op.name = name; + op.type = type; + op.cookie = cookie; + op.tag = tag; + op.description = description; + op.duration = duration; + op.flags = flags; + bufferlist in; + ::encode(op, in); + rados_op.exec("lock", "lock", in); + } + + int lock(IoCtx& ioctx, + string& oid, + string& name, ClsLockType type, + string& cookie, string& tag, + string description, utime_t& duration, uint8_t flags) + { + ObjectWriteOperation op; + lock(op, name, type, cookie, tag, description, duration, flags); + return ioctx.operate(oid, &op); + } + + void unlock(ObjectWriteOperation& rados_op, + string& name, string& cookie) + { + cls_lock_unlock_op op; + op.name = name; + op.cookie = cookie; + bufferlist in; + ::encode(op, in); + + rados_op.exec("lock", "unlock", in); + } + + int unlock(IoCtx& ioctx, string& oid, + string& name, string& cookie) + { + ObjectWriteOperation op; + unlock(op, name, cookie); + return ioctx.operate(oid, &op); + } + + void break_lock(ObjectWriteOperation& rados_op, + string& name, string& cookie, + entity_name_t& locker) + { + cls_lock_break_op op; + op.name = name; + op.cookie = cookie; + op.locker = locker; + bufferlist in; + ::encode(op, in); + rados_op.exec("lock", "break_lock", in); + } + + int break_lock(IoCtx& ioctx, string& oid, + string& name, string& cookie, + entity_name_t& locker) + { + ObjectWriteOperation op; + break_lock(op, name, cookie, locker); + return ioctx.operate(oid, &op); + } + + int list_locks(IoCtx& ioctx, string& oid, list *locks) + { + bufferlist in, out; + int r = ioctx.exec(oid, "lock", "list_locks", in, out); + if (r < 0) + return r; + + cls_lock_list_locks_reply ret; + bufferlist::iterator iter = out.begin(); + try { + ::decode(ret, iter); + } catch (buffer::error& err) { + cerr << __func__ << ": failed to decode response" << std::endl; + } + + *locks = ret.locks; + + return 0; + } + + int get_lock_info(IoCtx& ioctx, string& oid, string& lock, + map *lockers, + ClsLockType *lock_type, + string *tag) + { + bufferlist in, out; + cls_lock_get_info_op op; + op.name = lock; + ::encode(op, in); + int r = ioctx.exec(oid, "lock", "get_info", in, out); + if (r < 0) + return r; + + cls_lock_get_info_reply ret; + bufferlist::iterator iter = out.begin(); + try { + ::decode(ret, iter); + } catch (buffer::error& err) { + cerr << __func__ << ": failed to decode response" << std::endl; + } + + if (lockers) { + *lockers = ret.lockers; + } + + if (lock_type) { + *lock_type = ret.lock_type; + } + + if (tag) { + *tag = ret.tag; + } + + return 0; + } + + void Lock::lock_shared(ObjectWriteOperation& op) + { + lock(op, name, LOCK_SHARED, + cookie, tag, description, duration, flags); + } + + int Lock::lock_shared(IoCtx& ioctx, string& oid) + { + return lock(ioctx, oid, name, LOCK_SHARED, + cookie, tag, description, duration, flags); + } + + void Lock::lock_exclusive(ObjectWriteOperation& op) + { + lock(op, name, LOCK_EXCLUSIVE, + cookie, tag, description, duration, flags); + } + + int Lock::lock_exclusive(IoCtx& ioctx, string& oid) + { + return lock(ioctx, oid, name, LOCK_EXCLUSIVE, + cookie, tag, description, duration, flags); + } + + void Lock::unlock(ObjectWriteOperation& op) + { + rados::cls::lock::unlock(op, name, cookie); + } + + int Lock::unlock(IoCtx& ioctx, string& oid) + { + return rados::cls::lock::unlock(ioctx, oid, name, cookie); + } + + void Lock::break_lock(ObjectWriteOperation& op, entity_name_t& locker) + { + rados::cls::lock::break_lock(op, name, cookie, locker); + } + + int Lock::break_lock(IoCtx& ioctx, string& oid, entity_name_t& locker) + { + return rados::cls::lock::break_lock(ioctx, oid, name, cookie, locker); + } + } // namespace lock + } // namespace cls +} // namespace rados + diff --git a/src/cls/lock/cls_lock_client.h b/src/cls/lock/cls_lock_client.h new file mode 100644 index 0000000000000..1cb762d732c4b --- /dev/null +++ b/src/cls/lock/cls_lock_client.h @@ -0,0 +1,88 @@ +#ifndef CEPH_CLS_LOCK_CLIENT_H +#define CEPH_CLS_LOCK_CLIENT_H + + +#include "include/types.h" +#include "include/rados/librados.hpp" + +#include "cls/lock/cls_lock_types.h" + + +namespace rados { + namespace cls { + namespace lock { + + extern void lock(ObjectWriteOperation& rados_op, + std::string& name, ClsLockType type, + std::string& cookie, std::string& tag, + std::string description, utime_t& duration, uint8_t flags); + + extern int lock(IoCtx& ioctx, + std::string& oid, + std::string& name, ClsLockType type, + std::string& cookie, std::string& tag, + std::string description, utime_t& duration, uint8_t flags); + + extern void unlock(ObjectWriteOperation& rados_op, + std::string& name, std::string& cookie); + + extern int unlock(IoCtx& ioctx, std::string& oid, + std::string& name, std::string& cookie); + + extern void break_lock(ObjectWriteOperation& op, + std::string& name, std::string& cookie, + entity_name_t& locker); + + extern int break_lock(IoCtx& ioctx, std::string& oid, + std::string& name, std::string& cookie, + entity_name_t& locker); + + extern int list_locks(librados::IoCtx& ioctx, std::string& oid, list *locks); + extern int get_lock_info(librados::IoCtx& ioctx, std::string& oid, std::string& lock, + map *lockers, + ClsLockType *lock_type, + std::string *tag); + + class Lock { + std::string name; + std::string cookie; + std::string tag; + std::string description; + utime_t duration; + uint8_t flags; + + public: + + Lock(const std::string& _n) : name(_n), flags(0) {} + + void set_cookie(const std::string& c) { cookie = c; } + void set_tag(const std::string& t) { tag = t; } + void set_description(const std::string& desc) { description = desc; } + void set_duration(const utime_t& e) { duration = e; } + void set_renew(bool renew) { + if (renew) { + flags |= LOCK_FLAG_RENEW; + } else { + flags &= ~LOCK_FLAG_RENEW; + } + } + + /* ObjectWriteOperation */ + void lock_exclusive(ObjectWriteOperation& ioctx); + void lock_shared(ObjectWriteOperation& ioctx); + void unlock(ObjectWriteOperation& ioctx); + void break_lock(ObjectWriteOperation& ioctx, entity_name_t& locker); + + /* IoCtx*/ + int lock_exclusive(IoCtx& ioctx, std::string& oid); + int lock_shared(IoCtx& ioctx, std::string& oid); + int unlock(IoCtx& ioctx, std::string& oid); + int break_lock(IoCtx& ioctx, std::string& oid, entity_name_t& locker); + }; + + } // namespace lock + } // namespace cls +} // namespace rados + +#endif + diff --git a/src/cls/lock/cls_lock_ops.cc b/src/cls/lock/cls_lock_ops.cc new file mode 100644 index 0000000000000..fdbfc539f1d65 --- /dev/null +++ b/src/cls/lock/cls_lock_ops.cc @@ -0,0 +1,172 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/types.h" +#include "msg/msg_types.h" +#include "common/Formatter.h" + +#include "cls/lock/cls_lock_types.h" +#include "cls/lock/cls_lock_ops.h" + + + +static void generate_lock_id(cls_lock_id_t& i, int n, const string& cookie) +{ + i.locker = entity_name_t(entity_name_t::CLIENT(n)); + i.cookie = cookie; +} + +void cls_lock_lock_op::dump(Formatter *f) const +{ + f->dump_string("name", name); + f->dump_string("type", cls_lock_type_str(type)); + f->dump_string("cookie", cookie); + f->dump_string("tag", tag); + f->dump_string("description", description); + f->dump_stream("duration") << duration; + f->dump_int("flags", (int)flags); +} + +void cls_lock_lock_op::generate_test_instances(list& o) +{ + cls_lock_lock_op *i = new cls_lock_lock_op; + i->name = "name"; + i->type = LOCK_SHARED; + i->cookie = "cookie"; + i->tag = "tag"; + i->description = "description"; + i->duration = utime_t(5, 0); + i->flags = LOCK_FLAG_RENEW; + o.push_back(i); + o.push_back(new cls_lock_lock_op); +} + +void cls_lock_unlock_op::dump(Formatter *f) const +{ + f->dump_string("name", name); + f->dump_string("cookie", cookie); +} + +void cls_lock_unlock_op::generate_test_instances(list& o) +{ + cls_lock_unlock_op *i = new cls_lock_unlock_op; + i->name = "name"; + i->cookie = "cookie"; + o.push_back(i); + o.push_back(new cls_lock_unlock_op); +} + +void cls_lock_break_op::dump(Formatter *f) const +{ + f->dump_string("name", name); + f->dump_string("cookie", cookie); + f->dump_stream("locker") << locker; +} + +void cls_lock_break_op::generate_test_instances(list& o) +{ + cls_lock_break_op *i = new cls_lock_break_op; + i->name = "name"; + i->cookie = "cookie"; + i->locker = entity_name_t(entity_name_t::CLIENT(1)); + o.push_back(i); + o.push_back(new cls_lock_break_op); +} + +void cls_lock_get_info_op::dump(Formatter *f) const +{ + f->dump_string("name", name); +} + +void cls_lock_get_info_op::generate_test_instances(list& o) +{ + cls_lock_get_info_op *i = new cls_lock_get_info_op; + i->name = "name"; + o.push_back(i); + o.push_back(new cls_lock_get_info_op); +} + +static void generate_test_addr(entity_addr_t& a, int nonce, int port) +{ + a.set_nonce(nonce); + a.set_family(AF_INET); + a.set_in4_quad(0, 127); + a.set_in4_quad(0, 0); + a.set_in4_quad(0, 1); + a.set_in4_quad(0, 2); + a.set_port(port); +} + +void cls_lock_get_info_reply::dump(Formatter *f) const +{ + f->dump_string("lock_type", cls_lock_type_str(lock_type)); + f->dump_string("tag", tag); + f->open_array_section("lockers"); + map::const_iterator iter; + for (iter = lockers.begin(); iter != lockers.end(); ++iter) { + const cls_lock_id_t& id = iter->first; + const cls_lock_locker_info_t& info = iter->second; + f->open_object_section("object"); + f->dump_stream("locker") << id.locker; + f->dump_string("description", info.description); + f->dump_string("cookie", id.cookie); + f->dump_stream("duration") << info.duration; + f->dump_stream("addr") << info.addr; + f->close_section(); + } + f->close_section(); +} + +void cls_lock_get_info_reply::generate_test_instances(list& o) +{ + cls_lock_get_info_reply *i = new cls_lock_get_info_reply; + i->lock_type = LOCK_SHARED; + i->tag = "tag"; + cls_lock_id_t id1, id2; + entity_addr_t addr1, addr2; + generate_lock_id(id1, 1, "cookie1"); + generate_test_addr(addr1, 10, 20); + i->lockers[id1] = cls_lock_locker_info_t(utime_t(10, 0), addr1, "description1"); + generate_lock_id(id2, 2, "cookie2"); + generate_test_addr(addr2, 30, 40); + i->lockers[id2] = cls_lock_locker_info_t(utime_t(20, 0), addr2, "description2"); + + o.push_back(i); + o.push_back(new cls_lock_get_info_reply); +} + +void cls_lock_list_locks_reply::dump(Formatter *f) const +{ + list::const_iterator iter; + f->open_array_section("locks"); + for (iter = locks.begin(); iter != locks.end(); ++iter) { + f->open_array_section("object"); + f->dump_string("lock", *iter); + f->close_section(); + } + f->close_section(); +} + +void cls_lock_list_locks_reply::generate_test_instances(list& o) +{ + cls_lock_list_locks_reply *i = new cls_lock_list_locks_reply; + i->locks.push_back("lock1"); + i->locks.push_back("lock2"); + i->locks.push_back("lock3"); + + o.push_back(i); + o.push_back(new cls_lock_list_locks_reply); +} + + diff --git a/src/cls/lock/cls_lock_ops.h b/src/cls/lock/cls_lock_ops.h new file mode 100644 index 0000000000000..d0134573ac02a --- /dev/null +++ b/src/cls/lock/cls_lock_ops.h @@ -0,0 +1,173 @@ +#ifndef CEPH_CLS_LOCK_OPS_H +#define CEPH_CLS_LOCK_OPS_H + +#include "include/types.h" +#include "include/utime.h" +#include "cls/lock/cls_lock_types.h" + +struct cls_lock_lock_op +{ + string name; + ClsLockType type; + string cookie; + string tag; + string description; + utime_t duration; + uint8_t flags; + + cls_lock_lock_op() : type(LOCK_NONE), flags(0) {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(name, bl); + uint8_t t = (uint8_t)type; + ::encode(t, bl); + ::encode(cookie, bl); + ::encode(tag, bl); + ::encode(description, bl); + ::encode(duration, bl); + ::encode(flags, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(name, bl); + uint8_t t; + ::decode(t, bl); + type = (ClsLockType)t; + ::decode(cookie, bl); + ::decode(tag, bl); + ::decode(description, bl); + ::decode(duration, bl); + ::decode(flags, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_lock_op) + +struct cls_lock_unlock_op +{ + string name; + string cookie; + + cls_lock_unlock_op() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(name, bl); + ::encode(cookie, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(name, bl); + ::decode(cookie, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_unlock_op) + +struct cls_lock_break_op +{ + string name; + entity_name_t locker; + string cookie; + + cls_lock_break_op() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(name, bl); + ::encode(locker, bl); + ::encode(cookie, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(name, bl); + ::decode(locker, bl); + ::decode(cookie, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_break_op) + +struct cls_lock_get_info_op +{ + string name; + + cls_lock_get_info_op() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(name, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(name, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_get_info_op) + +struct cls_lock_get_info_reply +{ + map lockers; + ClsLockType lock_type; + string tag; + + cls_lock_get_info_reply() : lock_type(LOCK_NONE) {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(lockers, bl); + uint8_t t = (uint8_t)lock_type; + ::encode(t, bl); + ::encode(tag, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(lockers, bl); + uint8_t t; + ::decode(t, bl); + lock_type = (ClsLockType)t; + ::decode(tag, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_get_info_reply) + +struct cls_lock_list_locks_reply +{ + list locks; + + cls_lock_list_locks_reply() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(locks, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(locks, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_list_locks_reply) + +#endif diff --git a/src/cls/lock/cls_lock_types.cc b/src/cls/lock/cls_lock_types.cc new file mode 100644 index 0000000000000..d8b47d7805d4d --- /dev/null +++ b/src/cls/lock/cls_lock_types.cc @@ -0,0 +1,70 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/types.h" +#include "msg/msg_types.h" +#include "common/Formatter.h" + +#include "cls/lock/cls_lock_types.h" + + + +static void generate_lock_id(cls_lock_id_t& i, int n, const string& cookie) +{ + i.locker = entity_name_t(entity_name_t::CLIENT(n)); + i.cookie = cookie; +} + +void cls_lock_id_t::dump(Formatter *f) const +{ + f->dump_stream("locker") << locker; + f->dump_string("cookie", cookie); +} + +void cls_lock_id_t::generate_test_instances(list& o) +{ + cls_lock_id_t *i = new cls_lock_id_t; + generate_lock_id(*i, 1, "cookie"); + o.push_back(i); + o.push_back(new cls_lock_id_t); +} + +void cls_lock_locker_info_t::dump(Formatter *f) const +{ + f->dump_stream("duration") << duration; + f->dump_stream("addr") << addr; + f->dump_string("description", description); +} + +static void generate_test_addr(entity_addr_t& a, int nonce, int port) +{ + a.set_nonce(nonce); + a.set_family(AF_INET); + a.set_in4_quad(0, 127); + a.set_in4_quad(0, 0); + a.set_in4_quad(0, 1); + a.set_in4_quad(0, 2); + a.set_port(port); +} + +void cls_lock_locker_info_t::generate_test_instances(list& o) +{ + cls_lock_locker_info_t *i = new cls_lock_locker_info_t; + i->duration = utime_t(5, 0); + generate_test_addr(i->addr, 1, 2); + i->description = "description"; + o.push_back(i); + o.push_back(new cls_lock_locker_info_t); +} + diff --git a/src/cls/lock/cls_lock_types.h b/src/cls/lock/cls_lock_types.h new file mode 100644 index 0000000000000..a92c757c5dc4d --- /dev/null +++ b/src/cls/lock/cls_lock_types.h @@ -0,0 +1,91 @@ +#ifndef CEPH_CLS_LOCK_TYPES_H +#define CEPH_CLS_LOCK_TYPES_H + +#include "include/types.h" +#include "include/utime.h" + +/* lock flags */ +#define LOCK_FLAG_RENEW 0x1 /* idempotent lock acquire */ + +enum ClsLockType { + LOCK_NONE = 0, + LOCK_EXCLUSIVE = 1, + LOCK_SHARED = 2, +}; + +static inline const char *cls_lock_type_str(ClsLockType type) +{ + switch (type) { + case LOCK_NONE: + return "none"; + case LOCK_EXCLUSIVE: + return "exclusive"; + case LOCK_SHARED: + return "shared"; + default: + return ""; + } +} + +struct cls_lock_id_t { + entity_name_t locker; + string cookie; + + cls_lock_id_t() {} + cls_lock_id_t(entity_name_t& _n, const string& _c) : locker(_n), cookie(_c) {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(locker, bl); + ::encode(cookie, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(locker, bl); + ::decode(cookie, bl); + DECODE_FINISH(bl); + } + + bool operator<(const cls_lock_id_t& rhs) const { + if (locker == rhs.locker) + return cookie.compare(rhs.cookie) < 0; + if (locker < rhs.locker) + return true; + return false; + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_id_t) + +struct cls_lock_locker_info_t +{ + utime_t duration; + entity_addr_t addr; + string description; + + cls_lock_locker_info_t() {} + cls_lock_locker_info_t(const utime_t& _e, const entity_addr_t& _a, + const string& _d) : duration(_e), addr(_a), description(_d) {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(duration, bl); + ::encode(addr, bl); + ::encode(description, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(duration, bl); + ::decode(addr, bl); + ::decode(description, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + static void generate_test_instances(list& o); +}; +WRITE_CLASS_ENCODER(cls_lock_locker_info_t) + +#endif diff --git a/src/test/encoding/types.h b/src/test/encoding/types.h index fc49531892ed7..d2cba3259c42f 100644 --- a/src/test/encoding/types.h +++ b/src/test/encoding/types.h @@ -138,6 +138,18 @@ TYPE(cls_rbd_snap) #endif +#include "cls/lock/cls_lock_types.h" +TYPE(cls_lock_id_t) +TYPE(cls_lock_locker_info_t) +#include "cls/lock/cls_lock_ops.h" +TYPE(cls_lock_lock_op) +TYPE(cls_lock_unlock_op) +TYPE(cls_lock_break_op) +TYPE(cls_lock_get_info_op) +TYPE(cls_lock_get_info_reply) +TYPE(cls_lock_list_locks_reply) + + // --- messages --- #include "messages/MAuth.h" MESSAGE(MAuth) diff --git a/src/test/rados-api/cls_lock.cc b/src/test/rados-api/cls_lock.cc new file mode 100644 index 0000000000000..c54e98e357119 --- /dev/null +++ b/src/test/rados-api/cls_lock.cc @@ -0,0 +1,291 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include +#include + +#include "include/types.h" +#include "msg/msg_types.h" +#include "include/rados/librados.hpp" + +#include "test/rados-api/test.h" +#include "gtest/gtest.h" + +using namespace librados; + +#include "cls/lock/cls_lock_client.h" +#include "cls/lock/cls_lock_ops.h" + +using namespace rados::cls::lock; + +void lock_info(IoCtx& ioctx, string& oid, string& name, map& lockers, + ClsLockType *assert_type, string *assert_tag) +{ + ClsLockType lock_type = LOCK_NONE; + string tag; + lockers.clear(); + ASSERT_EQ(0, get_lock_info(ioctx, oid, name, &lockers, &lock_type, &tag)); + cout << "lock: " << name << std::endl; + cout << " lock_type: " << cls_lock_type_str(lock_type) << std::endl; + cout << " tag: " << tag << std::endl; + cout << " lockers:" << std::endl; + + if (assert_type) + ASSERT_EQ(*assert_type, lock_type); + + if (assert_tag) + ASSERT_EQ(*assert_tag, tag); + + map::iterator liter; + for (liter = lockers.begin(); liter != lockers.end(); ++liter) { + const cls_lock_id_t& locker = liter->first; + cout << " " << locker.locker << " duration=" << liter->second.duration + << " addr=" << liter->second.addr << " cookie=" << locker.cookie << std::endl; + } +} + +void lock_info(IoCtx& ioctx, string& oid, string& name, map& lockers) +{ + lock_info(ioctx, oid, name, lockers, NULL, NULL); +} + +TEST(ClsLock, TestMultiLocking) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + ClsLockType lock_type_shared = LOCK_SHARED; + ClsLockType lock_type_exclusive = LOCK_EXCLUSIVE; + + + Rados cluster2; + IoCtx ioctx2; + ASSERT_EQ("", connect_cluster_pp(cluster2)); + cluster2.ioctx_create(pool_name.c_str(), ioctx2); + + string oid = "foo"; + bufferlist bl; + string lock_name = "mylock"; + + ASSERT_EQ(0, ioctx.write(oid, bl, bl.length(), 0)); + + Lock l(lock_name); + + /* test lock object */ + + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + + /* test exclusive lock */ + ASSERT_EQ(-EEXIST, l.lock_exclusive(ioctx, oid)); + + /* test idempotency */ + l.set_renew(true); + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + + l.set_renew(false); + + /* test second client */ + Lock l2(lock_name); + ASSERT_EQ(-EBUSY, l2.lock_exclusive(ioctx2, oid)); + ASSERT_EQ(-EBUSY, l2.lock_shared(ioctx2, oid)); + + list locks; + ASSERT_EQ(0, list_locks(ioctx, oid, &locks)); + + ASSERT_EQ(1, (int)locks.size()); + list::iterator iter = locks.begin(); + map lockers; + lock_info(ioctx, oid, *iter, lockers, &lock_type_exclusive, NULL); + + ASSERT_EQ(1, (int)lockers.size()); + + /* test unlock */ + ASSERT_EQ(0, l.unlock(ioctx, oid)); + locks.clear(); + ASSERT_EQ(0, list_locks(ioctx, oid, &locks)); + + /* test shared lock */ + ASSERT_EQ(0, l2.lock_shared(ioctx2, oid)); + ASSERT_EQ(0, l.lock_shared(ioctx, oid)); + + locks.clear(); + ASSERT_EQ(0, list_locks(ioctx, oid, &locks)); + ASSERT_EQ(1, (int)locks.size()); + iter = locks.begin(); + lock_info(ioctx, oid, *iter, lockers, &lock_type_shared, NULL); + ASSERT_EQ(2, (int)lockers.size()); + + /* test break locks */ + entity_name_t name = entity_name_t::CLIENT(cluster.get_instance_id()); + entity_name_t name2 = entity_name_t::CLIENT(cluster2.get_instance_id()); + + l2.break_lock(ioctx2, oid, name); + lock_info(ioctx, oid, *iter, lockers); + ASSERT_EQ(1, (int)lockers.size()); + map::iterator liter = lockers.begin(); + const cls_lock_id_t& id = liter->first; + ASSERT_EQ(name2, id.locker); + + /* test lock tag */ + Lock l_tag(lock_name); + l_tag.set_tag("non-default tag"); + ASSERT_EQ(-EBUSY, l_tag.lock_shared(ioctx, oid)); + + + /* test modify description */ + string description = "new description"; + l.set_description(description); + ASSERT_EQ(0, l.lock_shared(ioctx, oid)); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} + +TEST(ClsLock, TestMeta) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + + + Rados cluster2; + IoCtx ioctx2; + ASSERT_EQ("", connect_cluster_pp(cluster2)); + cluster2.ioctx_create(pool_name.c_str(), ioctx2); + + string oid = "foo"; + bufferlist bl; + string lock_name = "mylock"; + + ASSERT_EQ(0, ioctx.write(oid, bl, bl.length(), 0)); + + Lock l(lock_name); + ASSERT_EQ(0, l.lock_shared(ioctx, oid)); + + /* test lock tag */ + Lock l_tag(lock_name); + l_tag.set_tag("non-default tag"); + ASSERT_EQ(-EBUSY, l_tag.lock_shared(ioctx2, oid)); + + + ASSERT_EQ(0, l.unlock(ioctx, oid)); + + /* test description */ + Lock l2(lock_name); + string description = "new description"; + l2.set_description(description); + ASSERT_EQ(0, l2.lock_shared(ioctx2, oid)); + + map lockers; + lock_info(ioctx, oid, lock_name, lockers, NULL, NULL); + ASSERT_EQ(1, (int)lockers.size()); + + map::iterator iter = lockers.begin(); + cls_lock_locker_info_t locker = iter->second; + ASSERT_EQ("new description", locker.description); + + ASSERT_EQ(0, l2.unlock(ioctx2, oid)); + + /* check new tag */ + string new_tag = "new_tag"; + l.set_tag(new_tag); + l.set_renew(true); + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + lock_info(ioctx, oid, lock_name, lockers, NULL, &new_tag); + ASSERT_EQ(1, (int)lockers.size()); + l.set_tag(""); + ASSERT_EQ(-EBUSY, l.lock_exclusive(ioctx, oid)); + l.set_tag(new_tag); + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} + +TEST(ClsLock, TestCookie) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + + string oid = "foo"; + string lock_name = "mylock"; + Lock l(lock_name); + + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + + /* new cookie */ + string cookie = "new cookie"; + l.set_cookie(cookie); + ASSERT_EQ(-EBUSY, l.lock_exclusive(ioctx, oid)); + ASSERT_EQ(-ENOENT, l.unlock(ioctx, oid)); + l.set_cookie(""); + ASSERT_EQ(0, l.unlock(ioctx, oid)); + + map lockers; + lock_info(ioctx, oid, lock_name, lockers); + ASSERT_EQ(0, (int)lockers.size()); + + l.set_cookie(cookie); + ASSERT_EQ(0, l.lock_shared(ioctx, oid)); + l.set_cookie(""); + ASSERT_EQ(0, l.lock_shared(ioctx, oid)); + + lock_info(ioctx, oid, lock_name, lockers); + ASSERT_EQ(2, (int)lockers.size()); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} + +TEST(ClsLock, TestMultipleLocks) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + + string oid = "foo"; + Lock l("lock1"); + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + + Lock l2("lock2"); + ASSERT_EQ(0, l2.lock_exclusive(ioctx, oid)); + + list locks; + ASSERT_EQ(0, list_locks(ioctx, oid, &locks)); + + ASSERT_EQ(2, (int)locks.size()); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} + +TEST(ClsLock, TestLockDuration) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + + string oid = "foo"; + Lock l("lock"); + l.set_duration(utime_t(5, 0)); + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + ASSERT_EQ(-EEXIST, l.lock_exclusive(ioctx, oid)); + + sleep(5); + ASSERT_EQ(0, l.lock_exclusive(ioctx, oid)); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} -- 2.39.5