]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: unit test cases and minor fixes for mirror::DisableRequest 11568/head
authorJason Dillaman <dillaman@redhat.com>
Wed, 19 Oct 2016 20:26:46 +0000 (16:26 -0400)
committerJason Dillaman <dillaman@redhat.com>
Mon, 31 Oct 2016 14:57:14 +0000 (10:57 -0400)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/librbd/mirror/DisableRequest.cc
src/librbd/mirror/DisableRequest.h
src/test/librbd/CMakeLists.txt
src/test/librbd/mirror/test_mock_DisableRequest.cc [new file with mode: 0644]

index d9eb0ad3626b17a437535989ce9bb4d3b3447f15..d2d6bea2f5eba2e8af47405ad0c1a4ae41212cf9 100644 (file)
@@ -25,8 +25,8 @@ namespace mirror {
 using util::create_rados_ack_callback;
 
 template <typename I>
-DisableRequest<I>::DisableRequest(I *image_ctx, bool force,
-                                              bool remove,  Context *on_finish)
+DisableRequest<I>::DisableRequest(I *image_ctx, bool force, bool remove,
+                                  Context *on_finish)
   : m_image_ctx(image_ctx), m_force(force), m_remove(remove),
     m_on_finish(on_finish), m_lock("mirror::DisableRequest::m_lock") {
 }
@@ -91,7 +91,7 @@ void DisableRequest<I>::send_get_tag_owner() {
   Context *ctx = util::create_context_callback<
       klass, &klass::handle_get_tag_owner>(this);
 
-  Journal<>::is_tag_owner(m_image_ctx, &m_is_primary, ctx);
+  Journal<I>::is_tag_owner(m_image_ctx, &m_is_primary, ctx);
 }
 
 template <typename I>
@@ -159,10 +159,9 @@ void DisableRequest<I>::send_notify_mirroring_watcher() {
   Context *ctx = util::create_context_callback<
     klass, &klass::handle_notify_mirroring_watcher>(this);
 
-  MirroringWatcher<>::notify_image_updated(m_image_ctx->md_ctx,
-                                           cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
-                                           m_image_ctx->id,
-                                           m_mirror_image.global_image_id, ctx);
+  MirroringWatcher<I>::notify_image_updated(
+    m_image_ctx->md_ctx, cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+    m_image_ctx->id, m_mirror_image.global_image_id, ctx);
 }
 
 template <typename I>
@@ -287,7 +286,10 @@ Context *DisableRequest<I>::handle_get_clients(int *result) {
     if (m_error_result < 0) {
       *result = m_error_result;
       return m_on_finish;
+    } else if (!m_remove) {
+      return m_on_finish;
     }
+
     // no mirror clients to unregister
     send_remove_mirror_image();
   }
@@ -395,10 +397,6 @@ Context *DisableRequest<I>::handle_unregister_client(
     return m_on_finish;
   }
 
-  if (!m_remove) {
-    return m_on_finish;
-  }
-
   send_get_clients();
   return nullptr;
 }
@@ -432,17 +430,14 @@ Context *DisableRequest<I>::handle_remove_mirror_image(int *result) {
   if (*result < 0) {
     lderr(cct) << "failed to remove mirror image: " << cpp_strerror(*result)
                << dendl;
+    return m_on_finish;
   }
 
   ldout(cct, 20) << this << " " << __func__
                  <<  ": removed image state from rbd_mirroring object" << dendl;
 
-  if (m_is_primary) {
-    send_notify_mirroring_watcher_removed();
-    return nullptr;
-  }
-
-  return m_on_finish;
+  send_notify_mirroring_watcher_removed();
+  return nullptr;
 }
 
 template <typename I>
@@ -454,7 +449,7 @@ void DisableRequest<I>::send_notify_mirroring_watcher_removed() {
   Context *ctx = util::create_context_callback<
     klass, &klass::handle_notify_mirroring_watcher_removed>(this);
 
-  MirroringWatcher<>::notify_image_updated(
+  MirroringWatcher<I>::notify_image_updated(
     m_image_ctx->md_ctx, cls::rbd::MIRROR_IMAGE_STATE_DISABLED, m_image_ctx->id,
     m_mirror_image.global_image_id, ctx);
 }
index f794ab1d922dac4bdf80dc3a19d2978bb33d0b1b..99cf04af90468874ff7c57b98d89bff767dfcd9f 100644 (file)
@@ -27,6 +27,9 @@ public:
     return new DisableRequest(image_ctx, force, remove, on_finish);
   }
 
+  DisableRequest(ImageCtxT *image_ctx, bool force, bool remove,
+                 Context *on_finish);
+
   void send();
 
 private:
@@ -77,9 +80,6 @@ private:
    * @endverbatim
    */
 
-  DisableRequest(ImageCtxT *image_ctx, bool force, bool remove,
-                 Context *on_finish);
-
   ImageCtxT *m_image_ctx;
   bool m_force;
   bool m_remove;
index 83bf6306616464979bce7f4b32c9b0f6fef6e03e..3a9f051452d7d875e3c1cd7396bbd9df79c671e5 100644 (file)
@@ -38,6 +38,7 @@ set(unittest_librbd_srcs
   journal/test_mock_OpenRequest.cc
   journal/test_mock_PromoteRequest.cc
   journal/test_mock_Replay.cc
+  mirror/test_mock_DisableRequest.cc
   object_map/test_mock_InvalidateRequest.cc
   object_map/test_mock_LockRequest.cc
   object_map/test_mock_RefreshRequest.cc
diff --git a/src/test/librbd/mirror/test_mock_DisableRequest.cc b/src/test/librbd/mirror/test_mock_DisableRequest.cc
new file mode 100644 (file)
index 0000000..14aec34
--- /dev/null
@@ -0,0 +1,581 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockOperations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "common/Mutex.h"
+#include "librbd/MirroringWatcher.h"
+#include "librbd/journal/PromoteRequest.h"
+#include "librbd/mirror/DisableRequest.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+  MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+  }
+};
+
+} // anonymous namespace
+
+template <>
+struct Journal<librbd::MockTestImageCtx> {
+  static Journal *s_instance;
+  static void is_tag_owner(librbd::MockTestImageCtx *, bool *is_primary,
+                           Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->is_tag_owner(is_primary, on_finish);
+  }
+
+  Journal() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD2(is_tag_owner, void(bool*, Context*));
+};
+
+Journal<librbd::MockTestImageCtx> *Journal<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+template <>
+struct MirroringWatcher<librbd::MockTestImageCtx> {
+  static MirroringWatcher *s_instance;
+  static void notify_image_updated(librados::IoCtx &io_ctx,
+                                   cls::rbd::MirrorImageState mirror_image_state,
+                                   const std::string &image_id,
+                                   const std::string &global_image_id,
+                                   Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->notify_image_updated(mirror_image_state, image_id,
+                                     global_image_id, on_finish);
+  }
+
+  MirroringWatcher() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD4(notify_image_updated, void(cls::rbd::MirrorImageState,
+                                          const std::string &,
+                                          const std::string &,
+                                          Context *));
+};
+
+MirroringWatcher<librbd::MockTestImageCtx> *MirroringWatcher<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace journal {
+
+template <>
+struct PromoteRequest<librbd::MockTestImageCtx> {
+  Context *on_finish = nullptr;
+  static PromoteRequest *s_instance;
+  static PromoteRequest *create(librbd::MockTestImageCtx *, bool force,
+                                Context *on_finish) {
+    assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  PromoteRequest() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+PromoteRequest<librbd::MockTestImageCtx> *PromoteRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace journal
+
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/DisableRequest.cc"
+template class librbd::mirror::DisableRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorDisableRequest : public TestMockFixture {
+public:
+  typedef DisableRequest<MockTestImageCtx> MockDisableRequest;
+  typedef Journal<MockTestImageCtx> MockJournal;
+  typedef MirroringWatcher<MockTestImageCtx> MockMirroringWatcher;
+  typedef journal::PromoteRequest<MockTestImageCtx> MockPromoteRequest;
+
+  void expect_get_mirror_image(MockTestImageCtx &mock_image_ctx,
+                               const cls::rbd::MirrorImage &mirror_image,
+                               int r) {
+    bufferlist bl;
+    ::encode(mirror_image, bl);
+
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_get"),
+                     _, _, _))
+      .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)),
+                      Return(r)));
+  }
+
+  void expect_is_tag_owner(MockTestImageCtx &mock_image_ctx,
+                           MockJournal &mock_journal,
+                           bool is_primary, int r) {
+    EXPECT_CALL(mock_journal, is_tag_owner(_, _))
+      .WillOnce(DoAll(SetArgPointee<0>(is_primary),
+                      WithArg<1>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))));
+  }
+
+  void expect_set_mirror_image(MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_set"),
+                     _, _, _))
+      .WillOnce(Return(r));
+  }
+
+  void expect_remove_mirror_image(MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_remove"),
+                     _, _, _))
+      .WillOnce(Return(r));
+  }
+
+  void expect_notify_image_updated(MockTestImageCtx &mock_image_ctx,
+                                   MockMirroringWatcher &mock_mirroring_watcher,
+                                   cls::rbd::MirrorImageState state,
+                                   const std::string &global_id, int r) {
+    EXPECT_CALL(mock_mirroring_watcher,
+                notify_image_updated(state, mock_image_ctx.id, global_id, _))
+      .WillOnce(WithArg<3>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)));
+  }
+
+  void expect_journal_client_list(MockTestImageCtx &mock_image_ctx,
+                                  const std::set<cls::journal::Client> &clients,
+                                  int r) {
+    bufferlist bl;
+    ::encode(clients, bl);
+
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(::journal::Journaler::header_oid(mock_image_ctx.id),
+                     _, StrEq("journal"), StrEq("client_list"), _, _, _))
+      .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)),
+                      Return(r)));
+  }
+
+  void expect_journal_client_unregister(MockTestImageCtx &mock_image_ctx,
+                                        const std::string &client_id,
+                                        int r) {
+    bufferlist bl;
+    ::encode(client_id, bl);
+
+    EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                exec(::journal::Journaler::header_oid(mock_image_ctx.id),
+                     _, StrEq("journal"), StrEq("client_unregister"),
+                     ContentsEqual(bl), _, _))
+      .WillOnce(Return(r));
+  }
+
+  void expect_journal_promote(MockTestImageCtx &mock_image_ctx,
+                              MockPromoteRequest &mock_promote_request, int r) {
+    EXPECT_CALL(mock_promote_request, send())
+      .WillOnce(FinishRequest(&mock_promote_request, r, &mock_image_ctx));
+  }
+
+  void expect_snap_remove(MockTestImageCtx &mock_image_ctx,
+                          const std::string &snap_name, int r) {
+    EXPECT_CALL(*mock_image_ctx.operations, execute_snap_remove(StrEq(snap_name), _))
+      .WillOnce(WithArg<1>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)));
+  }
+
+  template <typename T>
+  bufferlist encode(const T &t) {
+    bufferlist bl;
+    ::encode(t, bl);
+    return bl;
+  }
+
+};
+
+TEST_F(TestMockMirrorDisableRequest, Success) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+
+  expect_op_work_queue(mock_image_ctx);
+  expect_snap_remove(mock_image_ctx, "snap 1", 0);
+  expect_snap_remove(mock_image_ctx, "snap 2", 0);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", -ESHUTDOWN);
+  expect_journal_client_list(
+    mock_image_ctx, {
+      {"", encode(journal::ClientData{journal::ImageClientMeta{}})},
+      {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})},
+      {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{
+        "remote image id", {{"snap 1", boost::optional<uint64_t>(0)},
+                            {"snap 2", boost::optional<uint64_t>(0)}}}
+      })}
+    }, 0);
+  expect_journal_client_unregister(mock_image_ctx, "peer 1", 0);
+  expect_journal_client_unregister(mock_image_ctx, "peer 2", 0);
+  expect_journal_client_list(mock_image_ctx, {}, 0);
+  expect_remove_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLED,
+                              "global id", -ETIMEDOUT);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, SuccessNoRemove) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", 0);
+  expect_journal_client_list(mock_image_ctx, {}, 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, false, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, SuccessNonPrimary) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+  MockPromoteRequest mock_promote_request;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, false, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", 0);
+  expect_journal_promote(mock_image_ctx, mock_promote_request, 0);
+  expect_journal_client_list(mock_image_ctx, {}, 0);
+  expect_remove_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLED,
+                              "global id", 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, NonPrimaryError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, false, 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, false, &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, MirrorImageGetError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx, {}, -EBADMSG);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(-EBADMSG, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, IsTagOwnerError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, -EBADMSG);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(-EBADMSG, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, MirrorImageSetError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0);
+  expect_set_mirror_image(mock_image_ctx, -ENOENT);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, JournalPromoteError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+  MockPromoteRequest mock_promote_request;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, false, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", 0);
+  expect_journal_promote(mock_image_ctx, mock_promote_request, -EPERM);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx);
+  req->send();
+  ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, JournalClientListError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", 0);
+  expect_journal_client_list(mock_image_ctx, {}, -EBADMSG);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(-EBADMSG, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, SnapRemoveError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+
+  expect_op_work_queue(mock_image_ctx);
+  expect_snap_remove(mock_image_ctx, "snap 1", 0);
+  expect_snap_remove(mock_image_ctx, "snap 2", -EPERM);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", 0);
+  expect_journal_client_list(
+    mock_image_ctx, {
+      {"", encode(journal::ClientData{journal::ImageClientMeta{}})},
+      {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})},
+      {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{
+        "remote image id", {{"snap 1", boost::optional<uint64_t>(0)},
+                            {"snap 2", boost::optional<uint64_t>(0)}}}
+      })}
+    }, 0);
+  expect_journal_client_unregister(mock_image_ctx, "peer 1", 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, JournalClientUnregisterError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+
+  expect_op_work_queue(mock_image_ctx);
+  expect_snap_remove(mock_image_ctx, "snap 1", 0);
+  expect_snap_remove(mock_image_ctx, "snap 2", 0);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", 0);
+  expect_journal_client_list(
+    mock_image_ctx, {
+      {"", encode(journal::ClientData{journal::ImageClientMeta{}})},
+      {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})},
+      {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{
+        "remote image id", {{"snap 1", boost::optional<uint64_t>(0)},
+                            {"snap 2", boost::optional<uint64_t>(0)}}}
+      })}
+    }, 0);
+  expect_journal_client_unregister(mock_image_ctx, "peer 1", -EINVAL);
+  expect_journal_client_unregister(mock_image_ctx, "peer 2", 0);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorDisableRequest, MirrorImageRemoveError) {
+  REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  MockJournal mock_journal;
+  MockMirroringWatcher mock_mirroring_watcher;
+
+  expect_op_work_queue(mock_image_ctx);
+
+  InSequence seq;
+  expect_get_mirror_image(mock_image_ctx,
+                          {"global id", cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+                          0);
+  expect_is_tag_owner(mock_image_ctx, mock_journal, true, 0);
+  expect_set_mirror_image(mock_image_ctx, 0);
+  expect_notify_image_updated(mock_image_ctx, mock_mirroring_watcher,
+                              cls::rbd::MIRROR_IMAGE_STATE_DISABLING,
+                              "global id", 0);
+  expect_journal_client_list(mock_image_ctx, {}, 0);
+  expect_remove_mirror_image(mock_image_ctx, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace mirror
+} // namespace librbd
+