cls_method_handle_t h_snapshot_remove;
cls_method_handle_t h_get_all_features;
cls_method_handle_t h_copyup;
-cls_method_handle_t h_lock_image_exclusive;
-cls_method_handle_t h_lock_image_shared;
-cls_method_handle_t h_unlock_image;
-cls_method_handle_t h_break_lock;
-cls_method_handle_t h_list_locks;
cls_method_handle_t h_get_id;
cls_method_handle_t h_set_id;
cls_method_handle_t h_dir_get_id;
#define RBD_MAX_KEYS_READ 64
#define RBD_SNAP_KEY_PREFIX "snapshot_"
-#define RBD_LOCK_PREFIX "lock_"
-#define RBD_LOCK_TYPE_KEY RBD_LOCK_PREFIX "type"
-#define RBD_LOCKS_KEY RBD_LOCK_PREFIX "lockers"
-#define RBD_LOCK_EXCLUSIVE "exclusive"
-#define RBD_LOCK_SHARED "shared"
#define RBD_DIR_ID_KEY_PREFIX "id_"
#define RBD_DIR_NAME_KEY_PREFIX "name_"
return 0;
}
-/**
- * helper function to add a lock and update disk state.
- *
- * Input:
- * @param lock_type The type of lock, either RBD_LOCK_EXCLUSIVE or RBD_LOCK_SHARED
- * @param cookie The cookie to set in the lock
- *
- * @return 0 on success, or -errno on failure
- */
-int lock_image(cls_method_context_t hctx, string lock_type,
- const string &cookie)
-{
- bool exclusive = lock_type == RBD_LOCK_EXCLUSIVE;
-
- // see if there's already a locker
- set<pair<string, string> > lockers;
- string existing_lock_type;
- int r = read_key(hctx, RBD_LOCKS_KEY, &lockers);
- if (r != 0 && r != -ENOENT) {
- CLS_ERR("Could not read list of current lockers: %s", strerror(r));
- return r;
- }
- if (exclusive && r != -ENOENT && lockers.size()) {
- CLS_LOG(20, "could not exclusive-lock image, already locked");
- return -EBUSY;
- }
- if (lockers.size() && !exclusive) {
- // make sure existing lock is a shared lock
- r = read_key(hctx, RBD_LOCK_TYPE_KEY, &existing_lock_type);
- if (r != 0) {
- CLS_ERR("Could not read type of current locks off disk: %s", strerror(r));
- return r;
- }
- if (existing_lock_type != lock_type) {
- CLS_LOG(20, "cannot take shared lock on image, existing exclusive lock");
- return -EBUSY;
- }
- }
-
- // lock the image
- entity_inst_t locker;
- r = cls_get_request_origin(hctx, &locker);
- assert(r == 0);
- stringstream locker_stringstream;
- locker_stringstream << locker;
- pair<set<pair<string, string> >::iterator, bool> result;
- result = lockers.insert(make_pair(locker_stringstream.str(), cookie));
- if (!result.second) { // we didn't insert, because it already existed
- CLS_LOG(20, "could not insert locker -- already present");
- return -EEXIST;
- }
-
- map<string, bufferlist> lock_keys;
- ::encode(lockers, lock_keys[RBD_LOCKS_KEY]);
- ::encode(lock_type, lock_keys[RBD_LOCK_TYPE_KEY]);
-
- r = cls_cxx_map_set_vals(hctx, &lock_keys);
- if (r != 0) {
- CLS_ERR("error writing new lock state");
- }
- return r;
-}
-
-/**
- * Set an exclusive lock on an image for the activating client, if possible.
- *
- * Input:
- * @param lock_cookie A string cookie, defined by the locker.
- *
- * @returns 0 on success, -EINVAL if it can't decode the lock_cookie,
- * -EBUSY if the image is already locked, or -errno on (unexpected) failure.
- */
-int lock_image_exclusive(cls_method_context_t hctx,
- bufferlist *in, bufferlist *out)
-{
- CLS_LOG(20, "lock_image_exclusive");
- string lock_cookie;
- try {
- bufferlist::iterator iter = in->begin();
- ::decode(lock_cookie, iter);
- } catch (const buffer::error &err) {
- return -EINVAL;
- }
-
- return lock_image(hctx, RBD_LOCK_EXCLUSIVE, lock_cookie);
-}
-
-/**
- * Set an exclusive lock on an image, if possible.
- *
- * Input:
- * @param lock_cookie A string cookie, defined by the locker.
- *
- * @returns 0 on success, -EINVAL if it can't decode the lock_cookie,
- * -EBUSY if the image is exclusive locked, or -errno on (unexpected) failure.
- */
-int lock_image_shared(cls_method_context_t hctx,
- bufferlist *in, bufferlist *out)
-{
- CLS_LOG(20, "lock_image_shared");
- string lock_cookie;
- try {
- bufferlist::iterator iter = in->begin();
- ::decode(lock_cookie, iter);
- } catch (const buffer::error &err) {
- return -EINVAL;
- }
-
- return lock_image(hctx, RBD_LOCK_SHARED, lock_cookie);
-}
-
-/**
- * helper function to remove a lock from on disk and clean up state.
- *
- * @param inst The string representation of the locker's entity.
- * @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.
- */
-int remove_lock(cls_method_context_t hctx, const string& inst,
- const string& cookie)
-{
- // get current lockers
- set<pair<string, string> > lockers;
- string location = RBD_LOCKS_KEY;
- int r = read_key(hctx, location, &lockers);
- if (r != 0) {
- CLS_ERR("Could not read list of current lockers off disk: %s", strerror(r));
- return r;
- }
-
- // remove named locker from set
- pair<string, string> locker(inst, cookie);
- set<pair<string, string> >::iterator iter = lockers.find(locker);
- if (iter == lockers.end()) { // no such key
- return -ENOENT;
- }
- lockers.erase(iter);
-
- // encode and write new set to disk
- bufferlist locker_bufferlist;
- ::encode(lockers, locker_bufferlist);
- cls_cxx_map_set_val(hctx, location, &locker_bufferlist);
-
- return 0;
-}
-
-/**
- * Unlock an image which the activating client currently has locked.
- *
- * Input:
- * @param lock_cookie The user-defined cookie associated with the lock.
- *
- * @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.
- */
-int unlock_image(cls_method_context_t hctx,
- bufferlist *in, bufferlist *out)
-{
- CLS_LOG(20, "unlock_image");
- string lock_cookie;
- try {
- bufferlist::iterator iter = in->begin();
- ::decode(lock_cookie, iter);
- } catch (const buffer::error& err) {
- return -EINVAL;
- }
-
- entity_inst_t inst;
- int r = cls_get_request_origin(hctx, &inst);
- assert(r == 0);
- stringstream inst_stringstream;
- inst_stringstream << inst;
- return remove_lock(hctx, inst_stringstream.str(), lock_cookie);
-}
-
-/**
- * Break the lock on an image held by any client.
- *
- * Input:
- * @param locker The string representation of the locking client's entity.
- * @param lock_cookie The user-defined cookie associated with the lock.
- *
- * @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.
- */
-int break_lock(cls_method_context_t hctx,
- bufferlist *in, bufferlist *out)
-{
- CLS_LOG(20, "break_lock");
- string locker;
- string lock_cookie;
- try {
- bufferlist::iterator iter = in->begin();
- ::decode(locker, iter);
- ::decode(lock_cookie, iter);
- } catch (const buffer::error& err) {
- return -EINVAL;
- }
-
- return remove_lock(hctx, locker, lock_cookie);
-}
-
- /**
- * Retrieve a list of clients locking this object (presumably an rbd header),
- * as well as whether the lock is shared or exclusive.
- *
- * Input:
- * @param in is ignored.
- *
- * Output:
- * @param set<pair<string, string> > lockers The set of clients holding locks,
- * as <client, cookie> pairs.
- * @param exclusive_lock A bool, true if the lock is exclusive. If there are no
- * lockers, this is meaningless.
- *
- * @return 0 on success, -errno on failure.
- */
-int list_locks(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
-{
- CLS_LOG(20, "list_locks");
- string key = RBD_LOCKS_KEY;
- string exclusive_string;
- bool have_locks = true;
- int r = cls_cxx_map_get_val(hctx, key, out);
- if (r != 0 && r != -ENOENT) {
- CLS_ERR("Failure in reading list of current lockers: %s", strerror(r));
- return r;
- }
- if (r == -ENOENT) { // none listed
- set<pair<string, string> > empty_lockers;
- ::encode(empty_lockers, *out);
- have_locks = false;
- r = 0;
- }
- if (have_locks) {
- key = RBD_LOCK_TYPE_KEY;
- r = read_key(hctx, key, &exclusive_string);
- if (r < 0) {
- CLS_ERR("Failed to read lock type off disk: %s", strerror(r));
- }
- }
- ::encode((exclusive_string == RBD_LOCK_EXCLUSIVE), *out);
- return r;
-}
-
-
/**
* verify that the header object exists
*
cls_register_cxx_method(h_class, "copyup",
CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC,
copyup, &h_copyup);
- cls_register_cxx_method(h_class, "lock_exclusive",
- CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC,
- lock_image_exclusive, &h_lock_image_exclusive);
- cls_register_cxx_method(h_class, "lock_shared",
- CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC,
- lock_image_shared, &h_lock_image_shared);
- cls_register_cxx_method(h_class, "unlock_image",
- CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PUBLIC,
- unlock_image, &h_unlock_image);
- 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, "list_locks",
- CLS_METHOD_RD | CLS_METHOD_PUBLIC,
- list_locks, &h_list_locks);
cls_register_cxx_method(h_class, "get_parent",
CLS_METHOD_RD | CLS_METHOD_PUBLIC,
get_parent, &h_get_parent);
using ::librbd::cls_client::get_children;
using ::librbd::cls_client::get_snapcontext;
using ::librbd::cls_client::snapshot_list;
-using ::librbd::cls_client::list_locks;
-using ::librbd::cls_client::lock_image_exclusive;
-using ::librbd::cls_client::lock_image_shared;
-using ::librbd::cls_client::unlock_image;
-using ::librbd::cls_client::break_lock;
using ::librbd::cls_client::copyup;
using ::librbd::cls_client::get_id;
using ::librbd::cls_client::set_id;
ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
}
-TEST(cls_rbd, image_locking)
-{
- librados::Rados rados;
- librados::IoCtx ioctx;
- string pool_name = get_temp_pool_name();
-
- ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
- ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
-
- string oid = "test_locking_image";
- uint64_t size = 20 << 30;
- uint64_t features = 0;
- uint8_t order = 22;
- string object_prefix = "foo";
- string cookies[4];
- cookies[0] = "cookie0";
- cookies[1] = "cookie1";
- cookies[2] = "cookie2";
- cookies[3] = "cookie3";
-
- ASSERT_EQ(0, create_image(&ioctx, oid, size, order,
- features, object_prefix));
-
- // test that we can lock
- ASSERT_EQ(0, lock_image_exclusive(&ioctx, oid, cookies[0]));
- // and that we can't lock again
- ASSERT_EQ(-EBUSY, lock_image_exclusive(&ioctx, oid, cookies[1]));
- // and that unlock works
- ASSERT_EQ(0, unlock_image(&ioctx, oid, cookies[0]));
- // and that we can do shared locking
- ASSERT_EQ(0, lock_image_shared(&ioctx, oid, cookies[2]));
- ASSERT_EQ(0, lock_image_shared(&ioctx, oid, cookies[3]));
- // and that you can't exclusive lock with shared lockers
- ASSERT_EQ(-EBUSY, lock_image_exclusive(&ioctx, oid, cookies[1]));
- // but that you can after unlocking the shared lockers
- ASSERT_EQ(0, unlock_image(&ioctx, oid, cookies[2]));
- ASSERT_EQ(0, unlock_image(&ioctx, oid, cookies[3]));
- ASSERT_EQ(0, lock_image_exclusive(&ioctx, oid, cookies[1]));
- ASSERT_EQ(0, unlock_image(&ioctx, oid, cookies[1]));
-
- // test that we can list locks
- std::set<std::pair<std::string, std::string> > lockers;
- bool exclusive;
- ASSERT_EQ(0, list_locks(&ioctx, oid, lockers, exclusive));
- // and that no locks makes for an empty set
- int lockers_size = lockers.size();
- ASSERT_EQ(0, lockers_size);
-
- // test that two shared lockers compare properly
- ASSERT_EQ(0, lock_image_shared(&ioctx, oid, cookies[2]));
- ASSERT_EQ(0, lock_image_shared(&ioctx, oid, cookies[3]));
- ASSERT_EQ(0, list_locks(&ioctx, oid, lockers, exclusive));
- ASSERT_FALSE(exclusive);
- lockers_size = lockers.size();
- ASSERT_EQ(2, lockers_size);
- std::set<std::pair<std::string, std::string> >::iterator first, second;
- second = lockers.begin();
- first = second++;
- ASSERT_EQ(0, first->first.compare(second->first));
- std::string our_entity = first->first; // saved for later
- ASSERT_EQ(0, unlock_image(&ioctx, oid, cookies[2]));
- ASSERT_EQ(0, unlock_image(&ioctx, oid, cookies[3]));
-
- // and that a single exclusive looks right
- ASSERT_EQ(0, lock_image_exclusive(&ioctx, oid, cookies[0]));
- ASSERT_EQ(0, list_locks(&ioctx, oid, lockers, exclusive));
- lockers_size = lockers.size();
- ASSERT_EQ(1, lockers_size);
- ASSERT_TRUE(exclusive);
-
- // and do our best to test lock breaking
- ASSERT_EQ(0, break_lock(&ioctx, oid, our_entity, cookies[0]));
- // and that it actually removes the lock
- ASSERT_EQ(0, list_locks(&ioctx, oid, lockers, exclusive));
- lockers_size = lockers.size();
- ASSERT_EQ(0, lockers_size);
-
- // test that non-existent locks return errors correctly
- ASSERT_EQ(-ENOENT, unlock_image(&ioctx, oid, cookies[1]));
- ASSERT_EQ(-ENOENT, unlock_image(&ioctx, oid, cookies[0]));
- ASSERT_EQ(-ENOENT, break_lock(&ioctx, oid, our_entity, cookies[0]));
- // and make sure they still do that when somebody else does hold a lock
- ASSERT_EQ(0, lock_image_shared(&ioctx, oid, cookies[2]));
- ASSERT_EQ(-ENOENT, unlock_image(&ioctx, oid, cookies[1]));
- ASSERT_EQ(-ENOENT, unlock_image(&ioctx, oid, cookies[0]));
- ASSERT_EQ(-ENOENT, break_lock(&ioctx, oid, our_entity, cookies[0]));
- ASSERT_EQ(0, unlock_image(&ioctx, oid, cookies[2]));
-
- ioctx.close();
- ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
-}
-
TEST(cls_rbd, get_object_prefix)
{
librados::Rados rados;