initial code repo
[stor4nfv.git] / src / ceph / src / cls / lock / cls_lock.cc
diff --git a/src/ceph/src/cls/lock/cls_lock.cc b/src/ceph/src/cls/lock/cls_lock.cc
new file mode 100644 (file)
index 0000000..6e2ae4b
--- /dev/null
@@ -0,0 +1,598 @@
+// -*- 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 <errno.h>
+#include <map>
+
+#include "include/types.h"
+#include "include/utime.h"
+#include "objclass/objclass.h"
+
+#include "common/errno.h"
+#include "common/Clock.h"
+
+#include "cls/lock/cls_lock_types.h"
+#include "cls/lock/cls_lock_ops.h"
+
+#include "global/global_context.h"
+
+#include "include/compat.h"
+
+
+using namespace rados::cls::lock;
+
+
+CLS_VER(1,0)
+CLS_NAME(lock)
+
+#define LOCK_PREFIX    "lock."
+
+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();
+
+  map<locker_id_t, locker_info_t>::iterator iter = lock->lockers.begin();
+
+  while (iter != lock->lockers.end()) {
+    map<locker_id_t, locker_info_t>::iterator next = iter;
+    ++next;
+
+    struct locker_info_t& info = iter->second;
+    if (!info.expiration.is_zero() && info.expiration < 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, cls_get_client_features(hctx));
+
+  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 Duration of lock (in seconds). Zero means it doesn't expire.
+ * @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", cpp_strerror(r).c_str());
+    return r;
+  }
+  map<locker_id_t, locker_info_t>& lockers = linfo.lockers;
+  map<locker_id_t, locker_info_t>::iterator iter;
+
+  locker_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.empty()) {
+    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 expiration;
+  if (!duration.is_zero()) {
+    expiration = ceph_clock_now();
+    expiration += duration;
+
+  }
+  struct locker_info_t info(expiration, 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", cpp_strerror(r).c_str());
+    return r;
+  }
+
+  map<locker_id_t, locker_info_t>& lockers = linfo.lockers;
+  struct locker_id_t id(locker, cookie);
+
+  // remove named locker from set
+  map<locker_id_t, 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", cpp_strerror(r).c_str());
+    return r;
+  }
+
+  struct cls_lock_get_info_reply ret;
+
+  map<locker_id_t, 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, cls_get_client_features(hctx));
+
+  return 0;
+}
+
+
+/**
+ * Retrieve a list of locks for this object
+ *
+ * Input:
+ * @param in is ignored.
+ *
+ * Output:
+ * @param out contains encoded cls_list_locks_reply
+ *
+ * @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;
+}
+
+/**
+ * Assert that the object is currently locked
+ *
+ * Input:
+ * @param cls_lock_assert_op request input
+ *
+ * Output:
+ * @param none
+ *
+ * @return 0 on success, -errno on failure.
+ */
+int assert_locked(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+  CLS_LOG(20, "assert_locked");
+
+  cls_lock_assert_op op;
+  try {
+    bufferlist::iterator iter = in->begin();
+    ::decode(op, iter);
+  } catch (const buffer::error& err) {
+    return -EINVAL;
+  }
+
+  if (op.type != LOCK_EXCLUSIVE && op.type != LOCK_SHARED) {
+    return -EINVAL;
+  }
+
+  if (op.name.empty()) {
+    return -EINVAL;
+  }
+
+  // see if there's already a locker
+  lock_info_t linfo;
+  int r = read_lock(hctx, op.name, &linfo);
+  if (r < 0) {
+    CLS_ERR("Could not read lock info: %s", cpp_strerror(r).c_str());
+    return r;
+  }
+
+  if (linfo.lockers.empty()) {
+    CLS_LOG(20, "object not locked");
+    return -EBUSY;
+  }
+
+  if (linfo.lock_type != op.type) {
+    CLS_LOG(20, "lock type mismatch: current=%s, assert=%s",
+            cls_lock_type_str(linfo.lock_type), cls_lock_type_str(op.type));
+    return -EBUSY;
+  }
+
+  if (linfo.tag != op.tag) {
+    CLS_LOG(20, "lock tag mismatch: current=%s, assert=%s", linfo.tag.c_str(),
+            op.tag.c_str());
+    return -EBUSY;
+  }
+
+  entity_inst_t inst;
+  r = cls_get_request_origin(hctx, &inst);
+  assert(r == 0);
+
+  locker_id_t id;
+  id.cookie = op.cookie;
+  id.locker = inst.name;
+
+  map<locker_id_t, locker_info_t>::iterator iter = linfo.lockers.find(id);
+  if (iter == linfo.lockers.end()) {
+    CLS_LOG(20, "not locked by assert client");
+    return -EBUSY;
+  }
+  return 0;
+}
+
+/**
+ * Update the cookie associated with an object lock
+ *
+ * Input:
+ * @param cls_lock_set_cookie_op request input
+ *
+ * Output:
+ * @param none
+ *
+ * @return 0 on success, -errno on failure.
+ */
+int set_cookie(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+  CLS_LOG(20, "set_cookie");
+
+  cls_lock_set_cookie_op op;
+  try {
+    bufferlist::iterator iter = in->begin();
+    ::decode(op, iter);
+  } catch (const buffer::error& err) {
+    return -EINVAL;
+  }
+
+  if (op.type != LOCK_EXCLUSIVE && op.type != LOCK_SHARED) {
+    return -EINVAL;
+  }
+
+  if (op.name.empty()) {
+    return -EINVAL;
+  }
+
+  // see if there's already a locker
+  lock_info_t linfo;
+  int r = read_lock(hctx, op.name, &linfo);
+  if (r < 0) {
+    CLS_ERR("Could not read lock info: %s", cpp_strerror(r).c_str());
+    return r;
+  }
+
+  if (linfo.lockers.empty()) {
+    CLS_LOG(20, "object not locked");
+    return -EBUSY;
+  }
+
+  if (linfo.lock_type != op.type) {
+    CLS_LOG(20, "lock type mismatch: current=%s, assert=%s",
+            cls_lock_type_str(linfo.lock_type), cls_lock_type_str(op.type));
+    return -EBUSY;
+  }
+
+  if (linfo.tag != op.tag) {
+    CLS_LOG(20, "lock tag mismatch: current=%s, assert=%s", linfo.tag.c_str(),
+            op.tag.c_str());
+    return -EBUSY;
+  }
+
+  entity_inst_t inst;
+  r = cls_get_request_origin(hctx, &inst);
+  assert(r == 0);
+
+  locker_id_t id;
+  id.cookie = op.cookie;
+  id.locker = inst.name;
+
+  map<locker_id_t, locker_info_t>::iterator iter = linfo.lockers.find(id);
+  if (iter == linfo.lockers.end()) {
+    CLS_LOG(20, "not locked by client");
+    return -EBUSY;
+  }
+
+  id.cookie = op.new_cookie;
+  if (linfo.lockers.count(id) != 0) {
+    CLS_LOG(20, "lock cookie in-use");
+    return -EBUSY;
+  }
+
+  locker_info_t locker_info(iter->second);
+  linfo.lockers.erase(iter);
+
+  linfo.lockers[id] = locker_info;
+  r = write_lock(hctx, op.name, linfo);
+  if (r < 0) {
+    CLS_ERR("Could not update lock info: %s", cpp_strerror(r).c_str());
+    return r;
+  }
+  return 0;
+}
+
+CLS_INIT(lock)
+{
+  CLS_LOG(20, "Loaded lock class!");
+
+  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;
+  cls_method_handle_t h_assert_locked;
+  cls_method_handle_t h_set_cookie;
+
+  cls_register("lock", &h_class);
+  cls_register_cxx_method(h_class, "lock",
+                          CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
+                          lock_op, &h_lock_op);
+  cls_register_cxx_method(h_class, "unlock",
+                          CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
+                          unlock_op, &h_unlock_op);
+  cls_register_cxx_method(h_class, "break_lock",
+                          CLS_METHOD_RD | CLS_METHOD_WR,
+                          break_lock, &h_break_lock);
+  cls_register_cxx_method(h_class, "get_info",
+                          CLS_METHOD_RD,
+                          get_info, &h_get_info);
+  cls_register_cxx_method(h_class, "list_locks",
+                          CLS_METHOD_RD,
+                          list_locks, &h_list_locks);
+  cls_register_cxx_method(h_class, "assert_locked",
+                          CLS_METHOD_RD | CLS_METHOD_PROMOTE,
+                          assert_locked, &h_assert_locked);
+  cls_register_cxx_method(h_class, "set_cookie",
+                          CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
+                          set_cookie, &h_set_cookie);
+
+  return;
+}