X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fceph%2Fsrc%2Fcls%2Flock%2Fcls_lock.cc;fp=src%2Fceph%2Fsrc%2Fcls%2Flock%2Fcls_lock.cc;h=6e2ae4bbd1d1699570f0fd2745b640d02c55b835;hb=812ff6ca9fcd3e629e49d4328905f33eee8ca3f5;hp=0000000000000000000000000000000000000000;hpb=15280273faafb77777eab341909a3f495cf248d9;p=stor4nfv.git diff --git a/src/ceph/src/cls/lock/cls_lock.cc b/src/ceph/src/cls/lock/cls_lock.cc new file mode 100644 index 0000000..6e2ae4b --- /dev/null +++ b/src/ceph/src/cls/lock/cls_lock.cc @@ -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 +#include + +#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::iterator iter = lock->lockers.begin(); + + while (iter != lock->lockers.end()) { + map::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& lockers = linfo.lockers; + map::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& lockers = linfo.lockers; + struct locker_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", cpp_strerror(r).c_str()); + 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, 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 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; +} + +/** + * 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::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::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; +}