summaryrefslogtreecommitdiffstats
path: root/src/cls/lock/cls_lock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/cls/lock/cls_lock.cc')
-rw-r--r--src/cls/lock/cls_lock.cc648
1 files changed, 648 insertions, 0 deletions
diff --git a/src/cls/lock/cls_lock.cc b/src/cls/lock/cls_lock.cc
new file mode 100644
index 000000000..96d28461d
--- /dev/null
+++ b/src/cls/lock/cls_lock.cc
@@ -0,0 +1,648 @@
+// -*- 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 <sstream>
+
+#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 std::map;
+using std::string;
+
+using ceph::bufferlist;
+using namespace rados::cls::lock;
+
+CLS_VER(1,0)
+CLS_NAME(lock)
+
+#define LOCK_PREFIX "lock."
+
+static int clean_lock(cls_method_context_t hctx)
+{
+ int r = cls_cxx_remove(hctx);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+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 {
+ auto it = bl.cbegin();
+ decode(*lock, it);
+ } catch (const ceph::buffer::error &err) {
+ CLS_ERR("error decoding %s", key.c_str());
+ return -EIO;
+ }
+
+ /* now trim expired locks */
+
+ utime_t now = ceph_clock_now();
+
+ auto iter = lock->lockers.begin();
+
+ while (iter != lock->lockers.end()) {
+ struct locker_info_t& info = iter->second;
+ if (!info.expiration.is_zero() && info.expiration < now) {
+ CLS_LOG(20, "expiring locker");
+ iter = lock->lockers.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ if (lock->lockers.empty() && cls_lock_is_ephemeral(lock->lock_type)) {
+ r = clean_lock(hctx);
+ if (r < 0) {
+ CLS_ERR("error, on read, cleaning lock object %s", cpp_strerror(r).c_str());
+ }
+ }
+
+ return 0;
+}
+
+static int write_lock(cls_method_context_t hctx, const string& name, const lock_info_t& lock)
+{
+ using ceph::encode;
+ 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 = cls_lock_is_exclusive(lock_type);
+ lock_info_t linfo;
+ bool fail_if_exists = (flags & LOCK_FLAG_MAY_RENEW) == 0;
+ bool fail_if_does_not_exist = flags & LOCK_FLAG_MUST_RENEW;
+
+ CLS_LOG(20,
+ "requested lock_type=%s fail_if_exists=%d fail_if_does_not_exist=%d",
+ cls_lock_type_str(lock_type), fail_if_exists, fail_if_does_not_exist);
+ if (!cls_lock_is_valid(lock_type)) {
+ return -EINVAL;
+ }
+
+ if (name.empty())
+ return -EINVAL;
+
+ if (!fail_if_exists && fail_if_does_not_exist) {
+ // at most one of LOCK_FLAG_MAY_RENEW and LOCK_FLAG_MUST_RENEW may
+ // be set since they have different implications if the lock does
+ // not already exist
+ 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;
+ }
+
+ auto& lockers = linfo.lockers;
+
+ locker_id_t id;
+ id.cookie = cookie;
+ entity_inst_t inst;
+ r = cls_get_request_origin(hctx, &inst);
+ id.locker = inst.name;
+ ceph_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));
+ auto iter = lockers.find(id);
+ if (iter != lockers.end()) {
+ if (fail_if_exists && !fail_if_does_not_exist) {
+ return -EEXIST;
+ } else {
+ lockers.erase(iter); // remove old entry
+ }
+ } else if (fail_if_does_not_exist) {
+ return -ENOENT;
+ }
+
+ if (!lockers.empty()) {
+ if (exclusive) {
+ auto locker_lister =
+ [&lockers]() -> std::string {
+ std::stringstream locker_list;
+ locker_list << lockers;
+ return locker_list.str();
+ };
+ CLS_LOG(20, "could not exclusive-lock object, already locked by %s",
+ locker_lister().c_str());
+ 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;
+
+ }
+ // make all addrs of type legacy, because v2 clients speak v2 or v1,
+ // even depending on which OSD they are talking to, and the type
+ // isn't what uniquely identifies them. also, storing a v1 addr
+ // here means that old clients who get this locker_info won't see an
+ // old "msgr2:" prefix.
+ inst.addr.set_type(entity_addr_t::TYPE_LEGACY);
+
+ 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 {
+ auto iter = in->cbegin();
+ decode(op, iter);
+ } catch (const ceph::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;
+ }
+
+ auto& lockers = linfo.lockers;
+ struct locker_id_t id(locker, cookie);
+
+ // remove named locker from set
+ auto iter = lockers.find(id);
+ if (iter == lockers.end()) { // no such key
+ CLS_LOG(10, "locker %s [name: %s.%ld, cookie: %s] does not exist", name.c_str(),
+ locker.type_str(), locker.num(), cookie.c_str());
+ return -ENOENT;
+ }
+ lockers.erase(iter);
+
+ if (cls_lock_is_ephemeral(linfo.lock_type)) {
+ ceph_assert(lockers.empty());
+ r = clean_lock(hctx);
+ } else {
+ 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 {
+ auto iter = in->cbegin();
+ decode(op, iter);
+ } catch (const ceph::buffer::error& err) {
+ return -EINVAL;
+ }
+
+ entity_inst_t inst;
+ int r = cls_get_request_origin(hctx, &inst);
+ ceph_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 {
+ auto iter = in->cbegin();
+ decode(op, iter);
+ } catch (const ceph::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 {
+ auto iter = in->cbegin();
+ decode(op, iter);
+ } catch (const ceph::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;
+
+ for (auto 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;
+
+ size_t pos = sizeof(LOCK_PREFIX) - 1;
+ for (auto 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 {
+ auto iter = in->cbegin();
+ decode(op, iter);
+ } catch (const ceph::buffer::error& err) {
+ return -EINVAL;
+ }
+
+ if (!cls_lock_is_valid(op.type)) {
+ 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);
+ ceph_assert(r == 0);
+
+ locker_id_t id;
+ id.cookie = op.cookie;
+ id.locker = inst.name;
+
+ auto 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 {
+ auto iter = in->cbegin();
+ decode(op, iter);
+ } catch (const ceph::buffer::error& err) {
+ return -EINVAL;
+ }
+
+ if (!cls_lock_is_valid(op.type)) {
+ 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);
+ ceph_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;
+}