]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
cls_lock: objclass for advisory locking
authorYehuda Sadeh <yehuda@inktank.com>
Fri, 20 Jul 2012 19:59:07 +0000 (12:59 -0700)
committerYehuda Sadeh <yehuda@inktank.com>
Fri, 20 Jul 2012 19:59:07 +0000 (12:59 -0700)
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 <yehuda@inktank.com>
src/Makefile.am
src/cls/lock/cls_lock.cc [new file with mode: 0644]
src/cls/lock/cls_lock_client.cc [new file with mode: 0644]
src/cls/lock/cls_lock_client.h [new file with mode: 0644]
src/cls/lock/cls_lock_ops.cc [new file with mode: 0644]
src/cls/lock/cls_lock_ops.h [new file with mode: 0644]
src/cls/lock/cls_lock_types.cc [new file with mode: 0644]
src/cls/lock/cls_lock_types.h [new file with mode: 0644]
src/test/encoding/types.h
src/test/rados-api/cls_lock.cc [new file with mode: 0644]

index 8702f3cc6d42f66a328fc24fde9096a9a52c8f68..609eb4fa77af13ae366c60aa9dd7f0f0ff8830c5 100644 (file)
@@ -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 (file)
index 0000000..43915a4
--- /dev/null
@@ -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 <algorithm>
+#include <cstring>
+#include <cstdlib>
+#include <errno.h>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <vector>
+
+#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<cls_lock_id_t, cls_lock_locker_info_t> 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<cls_lock_id_t, cls_lock_locker_info_t>::iterator iter = lock->lockers.begin();
+
+  while (iter != lock->lockers.end()) {
+    map<cls_lock_id_t, cls_lock_locker_info_t>::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<cls_lock_id_t, cls_lock_locker_info_t>& lockers = linfo.lockers;
+  map<cls_lock_id_t, cls_lock_locker_info_t>::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<cls_lock_id_t, cls_lock_locker_info_t>& lockers = linfo.lockers;
+  struct cls_lock_id_t id(locker, cookie);
+
+  // remove named locker from set
+  map<cls_lock_id_t, cls_lock_locker_info_t>::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<cls_lock_id_t, cls_lock_locker_info_t>::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<string, bufferlist> attrs;
+
+  int r = cls_cxx_getxattrs(hctx, &attrs);
+  if (r < 0)
+    return r;
+
+  cls_lock_list_locks_reply ret;
+
+  map<string, bufferlist>::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 (file)
index 0000000..f3b600c
--- /dev/null
@@ -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 <sage@newdream.net>
+ *
+ * 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 <iostream>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#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<string> *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<cls_lock_id_t, cls_lock_locker_info_t> *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 (file)
index 0000000..1cb762d
--- /dev/null
@@ -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<std::string> *locks);
+      extern int get_lock_info(librados::IoCtx& ioctx, std::string& oid, std::string& lock,
+                               map<cls_lock_id_t, cls_lock_locker_info_t> *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 (file)
index 0000000..fdbfc53
--- /dev/null
@@ -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 <sage@newdream.net>
+ *
+ * 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<cls_lock_lock_op*>& 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<cls_lock_unlock_op*>& 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<cls_lock_break_op*>& 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<cls_lock_get_info_op*>& 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<cls_lock_id_t, cls_lock_locker_info_t>::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<cls_lock_get_info_reply*>& 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<string>::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<cls_lock_list_locks_reply*>& 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 (file)
index 0000000..d013457
--- /dev/null
@@ -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<cls_lock_lock_op*>& 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<cls_lock_unlock_op*>& 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<cls_lock_break_op*>& 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<cls_lock_get_info_op*>& o);
+};
+WRITE_CLASS_ENCODER(cls_lock_get_info_op)
+
+struct cls_lock_get_info_reply
+{
+  map<cls_lock_id_t, cls_lock_locker_info_t> 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<cls_lock_get_info_reply*>& o);
+};
+WRITE_CLASS_ENCODER(cls_lock_get_info_reply)
+
+struct cls_lock_list_locks_reply
+{
+  list<string> 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<cls_lock_list_locks_reply*>& 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 (file)
index 0000000..d8b47d7
--- /dev/null
@@ -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 <sage@newdream.net>
+ *
+ * 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<cls_lock_id_t*>& 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<cls_lock_locker_info_t*>& 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 (file)
index 0000000..a92c757
--- /dev/null
@@ -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 "<unknown>";
+    }
+}
+
+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<cls_lock_id_t*>& 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<cls_lock_locker_info_t *>& o);
+};
+WRITE_CLASS_ENCODER(cls_lock_locker_info_t)
+
+#endif
index fc49531892ed78502b39aae5b07b9b97e935140c..d2cba3259c42f5df66110d2fc93b64c507d43bfe 100644 (file)
@@ -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 (file)
index 0000000..c54e98e
--- /dev/null
@@ -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 <sage@newdream.net>
+ *
+ * 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 <iostream>
+#include <errno.h>
+
+#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<cls_lock_id_t, cls_lock_locker_info_t>& 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<cls_lock_id_t, cls_lock_locker_info_t>::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<cls_lock_id_t, cls_lock_locker_info_t>& 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<string> locks;
+  ASSERT_EQ(0, list_locks(ioctx, oid, &locks));
+
+  ASSERT_EQ(1, (int)locks.size());
+  list<string>::iterator iter = locks.begin();
+  map<cls_lock_id_t, cls_lock_locker_info_t> 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<cls_lock_id_t, cls_lock_locker_info_t>::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<cls_lock_id_t, cls_lock_locker_info_t> lockers;
+  lock_info(ioctx, oid, lock_name, lockers, NULL, NULL);
+  ASSERT_EQ(1, (int)lockers.size());
+
+  map<cls_lock_id_t, cls_lock_locker_info_t>::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<cls_lock_id_t, cls_lock_locker_info_t> 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<string> 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));
+}