]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: Add ImageWatcher unit test cases
authorJason Dillaman <dillaman@redhat.com>
Fri, 17 Oct 2014 13:05:22 +0000 (09:05 -0400)
committerJason Dillaman <dillaman@redhat.com>
Tue, 13 Jan 2015 01:02:06 +0000 (20:02 -0500)
Directly unit test the new ImageWatcher class to complement
the existing librbd integration tests of exclusive lock
handling.

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/include/buffer.h
src/librados/Makefile.am
src/librbd/internal.h
src/test/Makefile.am
src/test/librbd/test_ImageWatcher.cc [new file with mode: 0644]

index 0c87f961932936ec8efe9e2e449eacf84cc3abd4..fa5cfd08233ec4e6be92020491d1b7ebcadd9b51 100644 (file)
@@ -540,7 +540,7 @@ inline bool operator>=(bufferlist& l, bufferlist& r) {
   }
 }
 
-inline bool operator==(bufferlist &l, bufferlist &r) {
+inline bool operator==(const bufferlist &l, const bufferlist &r) {
   if (l.length() != r.length())
     return false;
   for (unsigned p = 0; p < l.length(); p++) {
index e5acc8c22e318dfb33a7b1a3304ac3b5341b1f79..8e6de3fcd200ffeb0b02ff72a0036da79e6de01a 100644 (file)
@@ -5,6 +5,11 @@ librados_internal_la_SOURCES = \
        librados/snap_set_diff.cc
 noinst_LTLIBRARIES += librados_internal.la
 
+librados_api_la_SOURCES = \
+       common/buffer.cc \
+       librados/librados.cc
+noinst_LTLIBRARIES += librados_api.la
+
 librados_la_SOURCES = \
        common/buffer.cc \
        librados/librados.cc
index 249a33d19a761cb0b860b1ea22c28c6d08910b1b..20c55af92f648296e7eab3fb891083b8f1320d9c 100644 (file)
@@ -82,7 +82,7 @@ namespace librbd {
   int snap_set(ImageCtx *ictx, const char *snap_name);
   int list(librados::IoCtx& io_ctx, std::vector<std::string>& names);
   int list_children(ImageCtx *ictx,
-                   std::set<pair<std::string, std::string> > & names);
+                   std::set<std::pair<std::string, std::string> > & names);
   int create(librados::IoCtx& io_ctx, const char *imgname, uint64_t size,
             int *order);
   int create(librados::IoCtx& io_ctx, const char *imgname, uint64_t size,
@@ -98,8 +98,8 @@ namespace librbd {
   int get_size(ImageCtx *ictx, uint64_t *size);
   int get_features(ImageCtx *ictx, uint64_t *features);
   int get_overlap(ImageCtx *ictx, uint64_t *overlap);
-  int get_parent_info(ImageCtx *ictx, string *parent_pool_name,
-                     string *parent_name, string *parent_snap_name);
+  int get_parent_info(ImageCtx *ictx, std::string *parent_pool_name,
+                     std::string *parent_name, std::string *parent_snap_name);
   int is_exclusive_lock_owner(ImageCtx *ictx, bool *is_owner);
 
   int remove(librados::IoCtx& io_ctx, const char *imgname,
@@ -165,7 +165,8 @@ namespace librbd {
   void image_info(const ImageCtx *ictx, image_info_t& info, size_t info_size);
   std::string get_block_oid(const std::string &object_prefix, uint64_t num,
                            bool old_format);
-  uint64_t oid_to_object_no(const string& oid, const string& object_prefix);
+  uint64_t oid_to_object_no(const std::string& oid,
+                           const std::string& object_prefix);
   int clip_io(ImageCtx *ictx, uint64_t off, uint64_t *len);
   int init_rbd_info(struct rbd_info *info);
   void init_rbd_header(struct rbd_obj_header_ondisk& ondisk,
index d714f1ad719e326e5d0e9334950f3386d9949361..4da23dbbbc6c2ba0e92c4a8f948c0fa78b21e185 100644 (file)
@@ -724,11 +724,21 @@ ceph_multi_stress_watch_SOURCES = test/multi_stress_watch.cc
 ceph_multi_stress_watch_LDADD = $(LIBRADOS) $(CEPH_GLOBAL) $(RADOS_TEST_LDADD)
 bin_DEBUGPROGRAMS += ceph_multi_stress_watch
 
-ceph_test_librbd_SOURCES = test/librbd/test_librbd.cc
-ceph_test_librbd_LDADD = $(LIBRBD) $(LIBRADOS) $(UNITTEST_LDADD) $(CEPH_GLOBAL) $(RADOS_TEST_LDADD)
+ceph_test_librbd_SOURCES = \
+       test/librbd/test_librbd.cc \
+       test/librbd/test_ImageWatcher.cc
+ceph_test_librbd_LDADD = \
+       librbd_api.la librbd_internal.la \
+       libcls_rbd_client.la libcls_lock_client.la \
+       librados_api.la $(LIBRADOS_DEPS) $(UNITTEST_LDADD) \
+       $(CEPH_GLOBAL) $(RADOS_TEST_LDADD)
 ceph_test_librbd_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 bin_DEBUGPROGRAMS += ceph_test_librbd
 
+if WITH_LTTNG
+ceph_test_librbd_LDADD += $(LIBRBD_TP)
+endif
+
 if LINUX
 # Force use of C++ linker with dummy.cc - LIBKRBD is a C++ library
 ceph_test_librbd_fsx_SOURCES = test/librbd/fsx.c common/dummy.cc
diff --git a/src/test/librbd/test_ImageWatcher.cc b/src/test/librbd/test_ImageWatcher.cc
new file mode 100644 (file)
index 0000000..eeea5f2
--- /dev/null
@@ -0,0 +1,714 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include "include/int_types.h"
+#include "include/stringify.h"
+#include "include/rados/librados.h"
+#include "include/rbd/librbd.hpp"
+#include "common/Cond.h"
+#include "common/Mutex.h"
+#include "common/RWLock.h"
+#include "cls/lock/cls_lock_client.h"
+#include "cls/lock/cls_lock_types.h"
+#include "librbd/AioCompletion.h"
+#include "librbd/internal.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageWatcher.h"
+#include "test/librados/test.h"
+#include "gtest/gtest.h"
+#include <boost/assign/list_of.hpp>
+#include <boost/bind.hpp>
+#include <iostream>
+#include <list>
+#include <set>
+#include <sstream>
+#include <vector>
+
+using namespace ceph;
+
+#define REQUIRE_FEATURE(feature) {       \
+  if (!is_feature_enabled(feature)) {    \
+    std::cout << "SKIPPING" << std::endl; \
+    return SUCCEED();                    \
+  }                                      \
+}
+
+static bool get_features(uint64_t *features) {
+  const char *c = getenv("RBD_FEATURES");
+  if (c == NULL) {
+    return false;
+  }
+
+  std::stringstream ss(c);
+  if (!(ss >> *features)) {
+    return false;
+  }
+  return true;
+}
+
+static int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx,
+                           const std::string &name, uint64_t size) {
+  uint64_t features = 0;
+  get_features(&features);
+  int order = 0;
+  return rbd.create2(ioctx, name.c_str(), size, features, &order);
+}
+
+static bool is_feature_enabled(uint64_t feature) {
+  uint64_t features;
+  return (get_features(&features) && (features & feature) == feature);
+}
+
+class TestImageWatcher : public ::testing::Test {
+public:
+
+  TestImageWatcher() : m_watch_ctx(NULL), m_aio_completion_restarts(),
+                      m_callback_lock("m_callback_lock")
+  {
+  }
+
+  enum NotifyOp {
+    NOTIFY_OP_ACQUIRED_LOCK = 0,
+    NOTIFY_OP_RELEASED_LOCK = 1,
+    NOTIFY_OP_REQUEST_LOCK  = 2,
+    NOTIFY_OP_HEADER_UPDATE = 3
+  };
+
+  class WatchCtx : public librados::WatchCtx2 {
+  public:
+    WatchCtx(TestImageWatcher &parent) : m_parent(parent) {}
+
+    int watch(const librbd::ImageCtx &ictx) {
+      m_header_oid = ictx.header_oid;
+      return m_parent.m_ioctx.watch2(m_header_oid, &m_handle, this);
+    }
+
+    int unwatch() {
+      return m_parent.m_ioctx.unwatch2(m_handle);
+    }
+
+    virtual void handle_notify(uint64_t notify_id,
+                               uint64_t cookie,
+                               uint64_t notifier_id,
+                               bufferlist& bl) {
+      try {
+       int op;
+       bufferlist payload;
+       bufferlist::iterator iter = bl.begin();
+       DECODE_START(1, iter);
+       ::decode(op, iter);
+       iter.copy_all(payload);
+       DECODE_FINISH(iter);
+
+       Mutex::Locker l(m_parent.m_callback_lock);
+       if (!m_parent.m_notify_acks.empty()) {
+         NotifyOp notify_op = m_parent.m_notify_acks.front().first;
+         if (notify_op != op) {
+           EXPECT_EQ(notify_op, static_cast<NotifyOp>(op));
+           m_parent.m_notify_acks.clear();
+         } else {
+           m_parent.m_ioctx.notify_ack(m_header_oid, notify_id, cookie,
+                                       m_parent.m_notify_acks.front().second);
+           m_parent.m_notify_acks.pop_front();
+         }
+       }
+
+       m_parent.m_notifies.push_back(
+         std::make_pair(static_cast<NotifyOp>(op), payload));
+       m_parent.m_callback_cond.Signal();
+      } catch (...) {
+       FAIL();
+      }
+    }
+
+    virtual void handle_failed_notify(uint64_t notify_id,
+                                      uint64_t cookie,
+                                      uint64_t notifier_id) {
+    }
+
+    virtual void handle_error(uint64_t cookie, int er) {
+    }
+
+    uint64_t get_handle() const {
+      return m_handle;
+    }
+
+  private:
+    TestImageWatcher &m_parent;
+    std::string m_header_oid;
+    uint64_t m_handle;
+  };
+
+  static void SetUpTestCase() {
+    _pool_name = get_temp_pool_name();
+    ASSERT_EQ("", create_one_pool_pp(_pool_name, _rados));
+  }
+
+  static void TearDownTestCase() {
+    ASSERT_EQ(0, destroy_one_pool_pp(_pool_name, _rados));
+  }
+
+  static std::string get_temp_image_name() {
+    ++_image_number;
+    return "image" + stringify(_image_number);
+  }
+
+  virtual void SetUp() {
+    ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), m_ioctx));
+
+    m_image_name = get_temp_image_name();
+    m_image_size = 2 << 20;
+    ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, m_image_name, m_image_size));
+  }
+
+  virtual void TearDown() {
+    unlock_image();
+    deregister_image_watch();
+
+    for (std::vector<librbd::ImageCtx *>::iterator iter = m_ictxs.begin();
+        iter != m_ictxs.end(); ++iter) {
+      librbd::close_image(*iter);
+    }
+
+    m_ioctx.close();
+  }
+
+  int open_image(const std::string &image_name, librbd::ImageCtx **ictx) {
+    *ictx = new librbd::ImageCtx(image_name.c_str(), "", NULL, m_ioctx, false);
+    m_ictxs.push_back(*ictx);
+    return librbd::open_image(*ictx);
+  }
+
+  int deregister_image_watch() {
+    if (m_watch_ctx != NULL) {
+      int r = m_watch_ctx->unwatch();
+      delete m_watch_ctx;
+      m_watch_ctx = NULL;
+      return r;
+    }
+    return 0;
+  }
+
+  int register_image_watch(librbd::ImageCtx &ictx) {
+    m_watch_ctx = new WatchCtx(*this);
+    return m_watch_ctx->watch(ictx);
+  }
+
+  bool wait_for_notifies(librbd::ImageCtx &ictx) {
+    Mutex::Locker l(m_callback_lock);
+    while (!m_notify_acks.empty()) {
+      int r = m_callback_cond.WaitInterval(ictx.cct, m_callback_lock,
+                                        utime_t(10, 0));
+      if (r != 0) {
+       break;
+      }
+    }
+    return m_notify_acks.empty();
+  }
+
+  int lock_image(librbd::ImageCtx &ictx, ClsLockType lock_type,
+                const std::string &cookie) {
+    int r = rados::cls::lock::lock(&m_ioctx, ictx.header_oid, RBD_LOCK_NAME,
+                                  lock_type, cookie, "internal", "", utime_t(),
+                                  0);
+    if (r == 0) {
+      m_lock_object = ictx.header_oid;
+      m_lock_cookie = cookie;
+    }
+    return r;
+  }
+
+  int unlock_image() {
+    int r = 0;
+    if (!m_lock_cookie.empty()) {
+      r = rados::cls::lock::unlock(&m_ioctx, m_lock_object, RBD_LOCK_NAME,
+                                  m_lock_cookie);
+      m_lock_cookie = "";
+    }
+    return r;
+  }
+
+  librbd::AioCompletion *create_aio_completion(librbd::ImageCtx &ictx) {
+    librbd::AioCompletion *aio_completion = new librbd::AioCompletion();
+    aio_completion->complete_cb = &handle_aio_completion;
+    aio_completion->complete_arg = this;
+
+    aio_completion->init_time(&ictx, librbd::AIO_TYPE_NONE);
+    m_aio_completions.insert(aio_completion);
+    return aio_completion;
+  }
+
+  static void handle_aio_completion(void *arg1, void *arg2) {
+    TestImageWatcher *test_image_watcher =
+      reinterpret_cast<TestImageWatcher *>(arg2);
+    Mutex::Locker l(test_image_watcher->m_callback_lock);
+    test_image_watcher->m_callback_cond.Signal();
+  }
+
+  int handle_restart_aio(librbd::AioCompletion *aio_completion) {
+    {
+      Mutex::Locker l(aio_completion->lock);
+      aio_completion->complete();
+    }
+
+    Mutex::Locker l(m_callback_lock);
+    m_aio_completions.erase(aio_completion);
+    delete aio_completion;
+    ++m_aio_completion_restarts;
+    m_callback_cond.Signal();
+    return 0;
+  }
+
+  bool wait_for_aio_completions(librbd::ImageCtx &ictx, int result) {
+    Mutex::Locker l(m_callback_lock);
+    while (!m_aio_completions.empty()) {
+      for (std::set<librbd::AioCompletion *>::iterator iter =
+            m_aio_completions.begin(); iter != m_aio_completions.end(); ) {
+       std::set<librbd::AioCompletion *>::iterator next(iter);
+       ++next;
+
+       librbd::AioCompletion *aio_completion = *iter;
+       if (!aio_completion->building) {
+         if (result != aio_completion->rval) {
+           EXPECT_EQ(result, aio_completion->rval);
+           return false;
+         }
+         m_aio_completions.erase(iter);
+       }
+       iter = next;
+      }
+      if (m_aio_completions.empty()) {
+       break;
+      }
+
+      int r = m_callback_cond.WaitInterval(ictx.cct, m_callback_lock,
+                                         utime_t(10, 0));
+      if (r != 0) {
+        break;
+      }
+    }
+    return (m_aio_completions.empty() &&
+           (result != 0 || m_aio_completion_restarts > 0));
+  }
+
+  static std::string _pool_name;
+  static librados::Rados _rados;
+  static uint64_t _image_number;
+
+  librados::IoCtx m_ioctx;
+  librbd::RBD m_rbd;
+
+  std::string m_image_name;
+  uint64_t m_image_size;
+
+  std::vector<librbd::ImageCtx *> m_ictxs;
+
+  typedef std::pair<NotifyOp, bufferlist> NotifyOpPayload;
+  typedef std::list<NotifyOpPayload> NotifyOpPayloads;
+
+  WatchCtx *m_watch_ctx;
+  NotifyOpPayloads m_notifies;
+  NotifyOpPayloads m_notify_acks;
+
+  std::set<librbd::AioCompletion *> m_aio_completions;
+  uint32_t m_aio_completion_restarts;
+
+  Mutex m_callback_lock;
+  Cond m_callback_cond;
+
+  std::string m_lock_object;
+  std::string m_lock_cookie;
+};
+
+std::string TestImageWatcher::_pool_name;
+librados::Rados TestImageWatcher::_rados;
+uint64_t TestImageWatcher::_image_number = 0;
+
+TEST_F(TestImageWatcher, IsLockSupported) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_TRUE(ictx->image_watcher);
+  ASSERT_TRUE(ictx->image_watcher->is_lock_supported());
+
+  ictx->read_only = true;
+  ASSERT_FALSE(ictx->image_watcher->is_lock_supported());
+  ictx->read_only = false;
+
+  ictx->features &= ~RBD_FEATURE_EXCLUSIVE_LOCK;
+  ASSERT_FALSE(ictx->image_watcher->is_lock_supported());
+  ictx->features |= RBD_FEATURE_EXCLUSIVE_LOCK;
+
+  ictx->snap_id = 1234;
+  ASSERT_FALSE(ictx->image_watcher->is_lock_supported());
+  ictx->snap_id = CEPH_NOSNAP;
+}
+
+TEST_F(TestImageWatcher, TryLock) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_TRUE(ictx->image_watcher);
+
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(0, ictx->image_watcher->try_lock());
+  ASSERT_TRUE(ictx->image_watcher->is_lock_owner());
+
+  std::map<rados::cls::lock::locker_id_t,
+           rados::cls::lock::locker_info_t> lockers;
+  ClsLockType lock_type;
+  ASSERT_EQ(0, rados::cls::lock::get_lock_info(&m_ioctx, ictx->header_oid,
+                                              RBD_LOCK_NAME, &lockers,
+                                              &lock_type, NULL));
+  ASSERT_EQ(LOCK_EXCLUSIVE, lock_type);
+  ASSERT_EQ(1U, lockers.size());
+}
+
+TEST_F(TestImageWatcher, TryLockNotifyAnnounceLocked) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, register_image_watch(*ictx));
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_ACQUIRED_LOCK, bufferlist()));
+
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(0, ictx->image_watcher->try_lock());
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+
+  ASSERT_TRUE(m_notify_acks.empty());
+  NotifyOpPayloads expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_ACQUIRED_LOCK, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+}
+
+TEST_F(TestImageWatcher, TryLockWithTimedOutOwner) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "auto 1234"));
+
+  // no watcher on the locked image means we can break the lock
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(0, ictx->image_watcher->try_lock());
+  ASSERT_TRUE(ictx->image_watcher->is_lock_owner());
+}
+
+TEST_F(TestImageWatcher, TryLockWithUserExclusiveLock) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked"));
+
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(-EBUSY, ictx->image_watcher->try_lock());
+  ASSERT_FALSE(ictx->image_watcher->is_lock_owner());
+
+  ASSERT_EQ(0, unlock_image());
+  ASSERT_EQ(0, ictx->image_watcher->try_lock());
+  ASSERT_TRUE(ictx->image_watcher->is_lock_owner());
+}
+
+TEST_F(TestImageWatcher, TryLockWithUserSharedLocked) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_SHARED, "manually locked"));
+
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(-EBUSY, ictx->image_watcher->try_lock());
+  ASSERT_FALSE(ictx->image_watcher->is_lock_owner());
+
+  ASSERT_EQ(0, unlock_image());
+  ASSERT_EQ(0, ictx->image_watcher->try_lock());
+  ASSERT_TRUE(ictx->image_watcher->is_lock_owner());
+}
+
+TEST_F(TestImageWatcher, UnlockNotLocked) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(0, ictx->image_watcher->unlock());
+}
+
+TEST_F(TestImageWatcher, UnlockNotifyReleaseLock) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, register_image_watch(*ictx));
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_ACQUIRED_LOCK, bufferlist()))(
+    std::make_pair(NOTIFY_OP_RELEASED_LOCK, bufferlist()));
+
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(0, ictx->image_watcher->try_lock());
+  ASSERT_EQ(0, ictx->image_watcher->unlock());
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+  ASSERT_TRUE(m_notify_acks.empty());
+  NotifyOpPayloads expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_ACQUIRED_LOCK, bufferlist()))(
+    std::make_pair(NOTIFY_OP_RELEASED_LOCK, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+}
+
+TEST_F(TestImageWatcher, UnlockBrokenLock) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  RWLock::WLocker l(ictx->owner_lock);
+  ASSERT_EQ(0, ictx->image_watcher->try_lock());
+
+  std::map<rados::cls::lock::locker_id_t,
+           rados::cls::lock::locker_info_t> lockers;
+  ClsLockType lock_type;
+  ASSERT_EQ(0, rados::cls::lock::get_lock_info(&m_ioctx, ictx->header_oid,
+                                               RBD_LOCK_NAME, &lockers,
+                                               &lock_type, NULL));
+  ASSERT_EQ(1U, lockers.size());
+  ASSERT_EQ(0, rados::cls::lock::break_lock(&m_ioctx, ictx->header_oid,
+                                           RBD_LOCK_NAME,
+                                           lockers.begin()->first.cookie,
+                                           lockers.begin()->first.locker));
+
+  ASSERT_EQ(0, ictx->image_watcher->unlock());
+}
+
+TEST_F(TestImageWatcher, RequestLock) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, register_image_watch(*ictx));
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE,
+                         "auto " + stringify(m_watch_ctx->get_handle())));
+
+  bufferlist bl;
+  {
+    ENCODE_START(1, 1, bl);
+    ::encode(0, bl);
+    ENCODE_FINISH(bl);
+  }
+
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bl));
+
+  {
+    RWLock::WLocker l(ictx->owner_lock);
+    ASSERT_EQ(0, ictx->image_watcher->request_lock(
+      boost::bind(&TestImageWatcher::handle_restart_aio, this, _1),
+      create_aio_completion(*ictx)));
+    ASSERT_EQ(0, ictx->image_watcher->request_lock(
+      boost::bind(&TestImageWatcher::handle_restart_aio, this, _1),
+      create_aio_completion(*ictx)));
+  }
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+  ASSERT_TRUE(m_notify_acks.empty());
+  NotifyOpPayloads expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+
+  ASSERT_EQ(0, unlock_image());
+
+  m_notifies.clear();
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_RELEASED_LOCK, bufferlist()))(
+    std::make_pair(NOTIFY_OP_ACQUIRED_LOCK, bufferlist()));
+
+  bl.clear();
+  {
+    ENCODE_START(1, 1, bl);
+    ::encode(NOTIFY_OP_RELEASED_LOCK, bl);
+    ENCODE_FINISH(bl);
+  }
+  ASSERT_EQ(0, m_ioctx.notify2(ictx->header_oid, bl, 5000, NULL));
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+  ASSERT_TRUE(m_notify_acks.empty());
+  expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_RELEASED_LOCK, bufferlist()))(
+    std::make_pair(NOTIFY_OP_ACQUIRED_LOCK, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+
+  ASSERT_TRUE(wait_for_aio_completions(*ictx, 0));
+}
+
+TEST_F(TestImageWatcher, RequestLockTimedOut) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, register_image_watch(*ictx));
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE,
+                         "auto " + stringify(m_watch_ctx->get_handle())));
+
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bufferlist()));
+
+  {
+    RWLock::WLocker l(ictx->owner_lock);
+    ASSERT_EQ(0, ictx->image_watcher->request_lock(
+      boost::bind(&TestImageWatcher::handle_restart_aio, this, _1),
+      create_aio_completion(*ictx)));
+  }
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+  ASSERT_TRUE(m_notify_acks.empty());
+  NotifyOpPayloads expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+
+  ASSERT_TRUE(wait_for_aio_completions(*ictx, 0));
+}
+
+TEST_F(TestImageWatcher, RequestLockTryLockRace) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, register_image_watch(*ictx));
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE,
+                          "auto " + stringify(m_watch_ctx->get_handle())));
+
+  bufferlist bl;
+  {
+    ENCODE_START(1, 1, bl);
+    ::encode(0, bl);
+    ENCODE_FINISH(bl);
+  }
+
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bl));
+
+  {
+    RWLock::WLocker l(ictx->owner_lock);
+    ASSERT_EQ(0, ictx->image_watcher->request_lock(
+      boost::bind(&TestImageWatcher::handle_restart_aio, this, _1),
+      create_aio_completion(*ictx)));
+  }
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+  ASSERT_TRUE(m_notify_acks.empty());
+  NotifyOpPayloads expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+
+  m_notifies.clear();
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_RELEASED_LOCK, bufferlist()));
+
+  bl.clear();
+  {
+    ENCODE_START(1, 1, bl);
+    ::encode(NOTIFY_OP_RELEASED_LOCK, bl);
+    ENCODE_FINISH(bl);
+  }
+  ASSERT_EQ(0, m_ioctx.notify2(ictx->header_oid, bl, 5000, NULL));
+  ASSERT_TRUE(wait_for_aio_completions(*ictx, 0));
+  RWLock::RLocker l(ictx->owner_lock);
+  ASSERT_FALSE(ictx->image_watcher->is_lock_owner());
+}
+
+TEST_F(TestImageWatcher, RequestLockPreTryLockFailed) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_SHARED, "manually 1234"));
+
+  {
+    RWLock::WLocker l(ictx->owner_lock);
+    ASSERT_EQ(0, ictx->image_watcher->request_lock(
+      boost::bind(&TestImageWatcher::handle_restart_aio, this, _1),
+      create_aio_completion(*ictx)));
+  }
+  ASSERT_TRUE(wait_for_aio_completions(*ictx, -EROFS));
+}
+
+TEST_F(TestImageWatcher, RequestLockPostTryLockFailed) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, register_image_watch(*ictx));
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE,
+                          "auto " + stringify(m_watch_ctx->get_handle())));
+
+  bufferlist bl;
+  {
+    ENCODE_START(1, 1, bl);
+    ::encode(0, bl);
+    ENCODE_FINISH(bl);
+  }
+
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bl));
+
+  {
+    RWLock::WLocker l(ictx->owner_lock);
+    ASSERT_EQ(0, ictx->image_watcher->request_lock(
+      boost::bind(&TestImageWatcher::handle_restart_aio, this, _1),
+      create_aio_completion(*ictx)));
+  }
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+  ASSERT_TRUE(m_notify_acks.empty());
+  NotifyOpPayloads expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_REQUEST_LOCK, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+
+  ASSERT_EQ(0, unlock_image());
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_SHARED, "manually 1234"));
+
+  m_notifies.clear();
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_RELEASED_LOCK, bufferlist()));
+
+  bl.clear();
+  {
+    ENCODE_START(1, 1, bl);
+    ::encode(NOTIFY_OP_RELEASED_LOCK, bl);
+    ENCODE_FINISH(bl);
+  }
+  ASSERT_EQ(0, m_ioctx.notify2(ictx->header_oid, bl, 5000, NULL));
+  ASSERT_TRUE(wait_for_aio_completions(*ictx, -EROFS));
+}
+
+TEST_F(TestImageWatcher, NotifyHeaderUpdate) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, register_image_watch(*ictx));
+
+  m_notify_acks = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_HEADER_UPDATE, bufferlist()));
+  librbd::ImageWatcher::notify_header_update(m_ioctx, ictx->header_oid);
+
+  ASSERT_TRUE(wait_for_notifies(*ictx));
+
+  ASSERT_TRUE(m_notify_acks.empty());
+  NotifyOpPayloads expected_notify_ops = boost::assign::list_of(
+    std::make_pair(NOTIFY_OP_HEADER_UPDATE, bufferlist()));
+  ASSERT_EQ(expected_notify_ops, m_notifies);
+}