]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: Add internal unit test cases
authorJason Dillaman <dillaman@redhat.com>
Tue, 21 Oct 2014 02:09:29 +0000 (22:09 -0400)
committerJason Dillaman <dillaman@redhat.com>
Tue, 13 Jan 2015 01:02:24 +0000 (20:02 -0500)
The new unit tests cover the modifications made to integrate
the internal librbd functionality with the new ImageWatcher.

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

index 91db8335ffe63604cb170731235a995698de48dd..206efc0075fe780d4393e632d3e17a2a49308b88 100644 (file)
@@ -102,6 +102,11 @@ int ImageWatcher::unregister_watch() {
   return m_image_ctx.md_ctx.unwatch2(m_handle);
 }
 
+bool ImageWatcher::has_pending_aio_operations() {
+  Mutex::Locker l(m_aio_request_lock);
+  return !m_aio_requests.empty();
+}
+
 void ImageWatcher::flush_aio_operations() {
   Mutex::Locker l(m_aio_request_lock);
   while (m_retrying_aio_requests || !m_aio_requests.empty()) {
index 57bf56d8cb51fd003ed7a06d555295f5eb4c5e2e..72d3305621c7fadbbb999ae82da865bff98da608 100644 (file)
@@ -35,6 +35,7 @@ namespace librbd {
     int unregister_watch();
     int get_watch_error();
 
+    bool has_pending_aio_operations();
     void flush_aio_operations();
 
     int try_lock();
index 4da23dbbbc6c2ba0e92c4a8f948c0fa78b21e185..099074d498148e19d70c696146cc86d921b9676d 100644 (file)
@@ -725,8 +725,10 @@ ceph_multi_stress_watch_LDADD = $(LIBRADOS) $(CEPH_GLOBAL) $(RADOS_TEST_LDADD)
 bin_DEBUGPROGRAMS += ceph_multi_stress_watch
 
 ceph_test_librbd_SOURCES = \
+       test/librbd/test_fixture.cc \
        test/librbd/test_librbd.cc \
-       test/librbd/test_ImageWatcher.cc
+       test/librbd/test_ImageWatcher.cc \
+       test/librbd/test_internal.cc
 ceph_test_librbd_LDADD = \
        librbd_api.la librbd_internal.la \
        libcls_rbd_client.la libcls_lock_client.la \
@@ -1058,6 +1060,7 @@ noinst_HEADERS += \
        test/librados/test.h \
        test/librados/TestCase.h \
        test/libradosstriper/TestCase.h \
+       test/librbd/test_fixture.h \
        test/ObjectMap/KeyValueDBMemory.h \
        test/omap_bench.h \
        test/osdc/FakeWriteback.h \
index eeea5f25f997cfb9a30b1c3fbbb4a02ffaaffd48..535bfe5a15fc28f9a2aa2b964e7970d2b7e9bd09 100644 (file)
@@ -1,5 +1,6 @@
 // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
 // vim: ts=8 sw=2 smarttab
+#include "test/librbd/test_fixture.h"
 #include "include/int_types.h"
 #include "include/stringify.h"
 #include "include/rados/librados.h"
 
 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 {
+class TestImageWatcher : public TestFixture {
 public:
 
   TestImageWatcher() : m_watch_ctx(NULL), m_aio_completion_restarts(),
@@ -138,44 +106,9 @@ public:
     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);
+    TestFixture::TearDown();
   }
 
   int deregister_image_watch() {
@@ -205,27 +138,6 @@ public:
     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();
@@ -290,18 +202,6 @@ public:
            (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;
 
@@ -315,14 +215,8 @@ public:
   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);
 
diff --git a/src/test/librbd/test_fixture.cc b/src/test/librbd/test_fixture.cc
new file mode 100644 (file)
index 0000000..f099949
--- /dev/null
@@ -0,0 +1,110 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include "test/librbd/test_fixture.h"
+#include "include/stringify.h"
+#include "cls/lock/cls_lock_client.h"
+#include "cls/lock/cls_lock_types.h"
+#include "librbd/internal.h"
+#include "test/librados/test.h"
+#include <iostream>
+#include <sstream>
+#include <stdlib.h>
+
+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;
+}
+
+bool is_feature_enabled(uint64_t feature) {
+  uint64_t features;
+  return (get_features(&features) && (features & feature) == feature);
+}
+
+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);
+}
+
+std::string TestFixture::_pool_name;
+librados::Rados TestFixture::_rados;
+uint64_t TestFixture::_image_number = 0;
+
+TestFixture::TestFixture() {
+}
+
+void TestFixture::SetUpTestCase() {
+  _pool_name = get_temp_pool_name();
+  ASSERT_EQ("", create_one_pool_pp(_pool_name, _rados));
+}
+
+void TestFixture::TearDownTestCase() {
+  ASSERT_EQ(0, destroy_one_pool_pp(_pool_name, _rados));
+}
+
+std::string TestFixture::get_temp_image_name() {
+  ++_image_number;
+  return "image" + stringify(_image_number);
+}
+
+void TestFixture::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));
+}
+
+void TestFixture::TearDown() {
+  unlock_image();
+  for (std::set<librbd::ImageCtx *>::iterator iter = m_ictxs.begin();
+       iter != m_ictxs.end(); ++iter) {
+    librbd::close_image(*iter);
+  }
+
+  m_ioctx.close();
+}
+
+int TestFixture::open_image(const std::string &image_name,
+                           librbd::ImageCtx **ictx) {
+  *ictx = new librbd::ImageCtx(image_name.c_str(), "", NULL, m_ioctx, false);
+  m_ictxs.insert(*ictx);
+  return librbd::open_image(*ictx);
+}
+
+void TestFixture::close_image(librbd::ImageCtx *ictx) {
+  m_ictxs.erase(ictx);
+  librbd::close_image(ictx);
+}
+
+int TestFixture::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 TestFixture::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;
+}
diff --git a/src/test/librbd/test_fixture.h b/src/test/librbd/test_fixture.h
new file mode 100644 (file)
index 0000000..d3246a7
--- /dev/null
@@ -0,0 +1,59 @@
+// -*- 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/rados/librados.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/ImageCtx.h"
+#include "gtest/gtest.h"
+#include <set>
+#include <string>
+
+using namespace ceph;
+
+bool get_features(uint64_t *features);
+bool is_feature_enabled(uint64_t feature);
+int create_image_pp(librbd::RBD &rbd, librados::IoCtx &ioctx,
+                    const std::string &name, uint64_t size);
+
+#define REQUIRE_FEATURE(feature) {       \
+  if (!is_feature_enabled(feature)) {    \
+    std::cout << "SKIPPING" << std::endl; \
+    return SUCCEED();                    \
+  }                                      \
+}
+
+class TestFixture : public ::testing::Test {
+public:
+
+  TestFixture();
+
+  static void SetUpTestCase();
+  static void TearDownTestCase();
+
+  static std::string get_temp_image_name();
+
+  virtual void SetUp();
+  virtual void TearDown();
+
+  int open_image(const std::string &image_name, librbd::ImageCtx **ictx);
+  void close_image(librbd::ImageCtx *ictx);
+
+  int lock_image(librbd::ImageCtx &ictx, ClsLockType lock_type,
+                 const std::string &cookie);
+  int unlock_image();
+
+  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::set<librbd::ImageCtx *> m_ictxs;
+
+  std::string m_lock_object;
+  std::string m_lock_cookie;
+};
diff --git a/src/test/librbd/test_internal.cc b/src/test/librbd/test_internal.cc
new file mode 100644 (file)
index 0000000..0bd747f
--- /dev/null
@@ -0,0 +1,274 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#include "test/librbd/test_fixture.h"
+#include "librbd/ImageWatcher.h"
+#include "librbd/internal.h"
+#include <boost/scope_exit.hpp>
+#include <utility>
+#include <vector>
+
+class TestInternal : public TestFixture {
+public:
+
+  TestInternal() {}
+
+  typedef std::vector<std::pair<std::string, bool> > Snaps;
+
+  virtual void TearDown() {
+    for (Snaps::iterator iter = m_snaps.begin(); iter != m_snaps.end(); ++iter) {
+      librbd::ImageCtx *ictx;
+      EXPECT_EQ(0, open_image(m_image_name, &ictx));
+      if (iter->second) {
+       EXPECT_EQ(0, librbd::snap_unprotect(ictx, iter->first.c_str()));
+      }
+      EXPECT_EQ(0, librbd::snap_remove(ictx, iter->first.c_str()));
+    }
+
+    TestFixture::TearDown();
+  }
+
+  int create_snapshot(const char *snap_name, bool snap_protect) {
+    librbd::ImageCtx *ictx;
+    int r = open_image(m_image_name, &ictx);
+    if (r < 0) {
+      return r;
+    }
+
+    r = librbd::snap_create(ictx, snap_name);
+    if (r < 0) {
+      return r;
+    }
+
+    m_snaps.push_back(std::make_pair(snap_name, snap_protect));
+    if (snap_protect) {
+      r = librbd::snap_protect(ictx, snap_name);
+      if (r < 0) {
+       return r;
+      }
+    }
+    close_image(ictx);
+    return 0;
+  }
+
+  Snaps m_snaps;
+};
+
+class DummyContext : public Context {
+public:
+  virtual void finish(int r) {
+  }
+};
+
+TEST_F(TestInternal, IsExclusiveLockOwner) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_FALSE(is_owner);
+
+  {
+    RWLock::WLocker l(ictx->owner_lock);
+    ASSERT_EQ(0, ictx->image_watcher->try_lock());
+  }
+
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, ResizeLocksImage) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  librbd::NoOpProgressContext no_op;
+  ASSERT_EQ(0, librbd::resize(ictx, m_image_size >> 1, no_op));
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, ResizeFailsToLockImage) {
+  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"));
+
+  librbd::NoOpProgressContext no_op;
+  ASSERT_EQ(-EROFS, librbd::resize(ictx, m_image_size >> 1, no_op));
+}
+
+TEST_F(TestInternal, SnapCreateLocksImage) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  ASSERT_EQ(0, librbd::snap_create(ictx, "snap1"));
+  BOOST_SCOPE_EXIT( (ictx) ) {
+    ASSERT_EQ(0, librbd::snap_remove(ictx, "snap1"));
+  } BOOST_SCOPE_EXIT_END;
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, SnapCreateFailsToLockImage) {
+  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"));
+
+  ASSERT_EQ(-EROFS, librbd::snap_create(ictx, "snap1"));
+}
+
+TEST_F(TestInternal, SnapRollbackLocksImage) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  ASSERT_EQ(0, create_snapshot("snap1", false));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  librbd::NoOpProgressContext no_op;
+  ASSERT_EQ(0, librbd::snap_rollback(ictx, "snap1", no_op));
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, SnapRollbackFailsToLockImage) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+
+  ASSERT_EQ(0, create_snapshot("snap1", false));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, lock_image(*ictx, LOCK_EXCLUSIVE, "manually locked"));
+
+  librbd::NoOpProgressContext no_op;
+  ASSERT_EQ(-EROFS, librbd::snap_rollback(ictx, "snap1", no_op));
+}
+
+TEST_F(TestInternal, SnapSetReleasesLock) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  ASSERT_EQ(0, create_snapshot("snap1", false));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+  ASSERT_EQ(0, librbd::snap_set(ictx, "snap1"));
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_FALSE(is_owner);
+}
+
+TEST_F(TestInternal, FlattenLocksImage) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING);
+
+  ASSERT_EQ(0, create_snapshot("snap1", true));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  uint64_t features;
+  ASSERT_EQ(0, librbd::get_features(ictx, &features));
+
+  std::string clone_name = get_temp_image_name();
+  int order = ictx->order;
+  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
+                            clone_name.c_str(), features, &order, 0, 0));
+
+  librbd::ImageCtx *ictx2;
+  ASSERT_EQ(0, open_image(clone_name, &ictx2));
+
+  librbd::NoOpProgressContext no_op;
+  ASSERT_EQ(0, librbd::flatten(ictx2, no_op));
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx2, &is_owner));
+  ASSERT_TRUE(is_owner);
+}
+
+TEST_F(TestInternal, FlattenFailsToLockImage) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_LAYERING);
+
+  ASSERT_EQ(0, create_snapshot("snap1", true));
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  uint64_t features;
+  ASSERT_EQ(0, librbd::get_features(ictx, &features));
+
+  std::string clone_name = get_temp_image_name();
+  int order = ictx->order;
+  ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx,
+                             clone_name.c_str(), features, &order, 0, 0));
+  BOOST_SCOPE_EXIT( (&m_ioctx) (clone_name) ) {
+    librbd::NoOpProgressContext no_op;
+    ASSERT_EQ(0, librbd::remove(m_ioctx, clone_name.c_str(), no_op));
+  } BOOST_SCOPE_EXIT_END;
+
+  librbd::ImageCtx *ictx2;
+  ASSERT_EQ(0, open_image(clone_name, &ictx2));
+
+  TestInternal *parent = this;
+  BOOST_SCOPE_EXIT( (parent) (ictx2) ) {
+    parent->close_image(ictx2);
+  } BOOST_SCOPE_EXIT_END;
+
+  ASSERT_EQ(0, lock_image(*ictx2, LOCK_EXCLUSIVE, "manually locked"));
+
+  librbd::NoOpProgressContext no_op;
+  ASSERT_EQ(-EROFS, librbd::flatten(ictx2, no_op));
+}
+
+TEST_F(TestInternal, AioWriteRequestsLock) {
+  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"));
+
+  std::string buffer(256, '1');
+  DummyContext *ctx = new DummyContext();
+  librbd::AioCompletion *c =
+    librbd::aio_create_completion_internal(ctx, librbd::rbd_ctx_cb);
+  ASSERT_EQ(0, aio_write(ictx, 0, buffer.size(), buffer.c_str(), c, 0));
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_FALSE(is_owner);
+
+  ASSERT_TRUE(ictx->image_watcher->has_pending_aio_operations());
+}
+
+TEST_F(TestInternal, AioDiscardRequestsLock) {
+  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"));
+
+  DummyContext *ctx = new DummyContext();
+  librbd::AioCompletion *c =
+    librbd::aio_create_completion_internal(ctx, librbd::rbd_ctx_cb);
+  ASSERT_EQ(0, aio_discard(ictx, 0, 256, c));
+
+  bool is_owner;
+  ASSERT_EQ(0, librbd::is_exclusive_lock_owner(ictx, &is_owner));
+  ASSERT_FALSE(is_owner);
+
+  ASSERT_TRUE(ictx->image_watcher->has_pending_aio_operations());
+}