]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: force promote for snapshot mirroring 30548/head
authorMykola Golub <mgolub@suse.com>
Sat, 30 Nov 2019 17:07:38 +0000 (17:07 +0000)
committerMykola Golub <mgolub@suse.com>
Tue, 10 Dec 2019 15:45:30 +0000 (15:45 +0000)
Signed-off-by: Mykola Golub <mgolub@suse.com>
12 files changed:
src/librbd/ImageState.cc
src/librbd/ImageState.h
src/librbd/mirror/PromoteRequest.cc
src/librbd/mirror/snapshot/CreateNonPrimaryRequest.cc
src/librbd/mirror/snapshot/CreateNonPrimaryRequest.h
src/librbd/mirror/snapshot/PromoteRequest.cc
src/librbd/mirror/snapshot/PromoteRequest.h
src/test/librbd/CMakeLists.txt
src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc
src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc [new file with mode: 0644]
src/test/librbd/mock/MockImageState.h
src/test/librbd/test_mirroring.cc

index 9cfa2141670b7664e914a214cc5fe713acb99c62..24cb10d3eff59577f611e3ced7bdc804336c3841 100644 (file)
@@ -435,12 +435,18 @@ int ImageState<I>::register_update_watcher(UpdateWatchCtx *watcher,
 }
 
 template <typename I>
-int ImageState<I>::unregister_update_watcher(uint64_t handle) {
+void ImageState<I>::unregister_update_watcher(uint64_t handle,
+                                              Context *on_finish) {
   CephContext *cct = m_image_ctx->cct;
   ldout(cct, 20) << __func__ << ": handle=" << handle << dendl;
 
+  m_update_watchers->unregister_watcher(handle, on_finish);
+}
+
+template <typename I>
+int ImageState<I>::unregister_update_watcher(uint64_t handle) {
   C_SaferCond ctx;
-  m_update_watchers->unregister_watcher(handle, &ctx);
+  unregister_update_watcher(handle, &ctx);
   return ctx.wait();
 }
 
index 9daa5137ed9ea5262919f4d2e164c419b0d49d51..e51f18e94bdd62ce9c0e0036fd885a73838a57d8 100644 (file)
@@ -46,6 +46,7 @@ public:
   void handle_prepare_lock_complete();
 
   int register_update_watcher(UpdateWatchCtx *watcher, uint64_t *handle);
+  void unregister_update_watcher(uint64_t handle, Context *on_finish);
   int unregister_update_watcher(uint64_t handle);
   void flush_update_watchers(Context *on_finish);
   void shut_down_update_watchers(Context *on_finish);
index 2905d13bff61ad7029aaa9ceef485c1d7696ff03..e2bc6bd0582fd2de6c9b7f74c0acdf7229f8a725 100644 (file)
@@ -76,7 +76,8 @@ void PromoteRequest<I>::promote() {
   if (m_mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_JOURNAL) {
     Journal<I>::promote(&m_image_ctx, ctx);
   } else if (m_mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT) {
-    auto req = mirror::snapshot::PromoteRequest<I>::create(&m_image_ctx, ctx);
+    auto req = mirror::snapshot::PromoteRequest<I>::create(&m_image_ctx,
+                                                           m_force, ctx);
     req->send();
   } else {
     lderr(cct) << "unknown image mirror mode: " << m_mirror_image.mode << dendl;
index 426117b69b7b0d3cba262806856317027aea2a7f..21f821f3d5f0544852e064da7b15e837162cc723 100644 (file)
@@ -100,7 +100,7 @@ void CreateNonPrimaryRequest<I>::handle_get_mirror_image(int r) {
     return;
   }
 
-  if (!util::can_create_non_primary_snapshot(m_image_ctx)) {
+  if (!is_orphan() && !util::can_create_non_primary_snapshot(m_image_ctx)) {
     finish(-EINVAL);
     return;
   }
@@ -143,9 +143,6 @@ void CreateNonPrimaryRequest<I>::handle_create_snapshot(int r) {
 
 template <typename I>
 void CreateNonPrimaryRequest<I>::write_image_state() {
-  CephContext *cct = m_image_ctx->cct;
-  ldout(cct, 20) << dendl;
-
   uint64_t snap_id;
   {
     std::shared_lock image_locker{m_image_ctx->image_lock};
@@ -158,6 +155,13 @@ void CreateNonPrimaryRequest<I>::write_image_state() {
     *m_snap_id = snap_id;
   }
 
+  if (is_orphan()) {
+    finish(0);
+  }
+
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
   auto ctx = create_context_callback<
     CreateNonPrimaryRequest<I>,
     &CreateNonPrimaryRequest<I>::handle_write_image_state>(this);
index a2ab6adc5c45da5abdba310a54e27cc2eb6ea2e4..2b0fb68cd27160a200963b592140e88fc9ce4cc9 100644 (file)
@@ -81,6 +81,10 @@ private:
 
   bufferlist m_out_bl;
 
+  bool is_orphan() const {
+    return m_primary_mirror_uuid.empty();
+  }
+
   void refresh_image();
   void handle_refresh_image(int r);
 
index c9e59e03065591c06c739bed5c59648624ac2441..9fe4bfe2060fb098e410f120fbd3a809829f183f 100644 (file)
@@ -2,14 +2,19 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "librbd/mirror/snapshot/PromoteRequest.h"
+#include "common/Timer.h"
 #include "common/dout.h"
 #include "common/errno.h"
 #include "cls/rbd/cls_rbd_client.h"
+#include "librbd/ExclusiveLock.h"
 #include "librbd/ImageCtx.h"
 #include "librbd/ImageState.h"
 #include "librbd/Operations.h"
 #include "librbd/Utils.h"
+#include "librbd/image/ListWatchersRequest.h"
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h"
 #include "librbd/mirror/snapshot/CreatePrimaryRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
 
 #define dout_subsys ceph_subsys_rbd
 #undef dout_prefix
@@ -20,6 +25,7 @@ namespace librbd {
 namespace mirror {
 namespace snapshot {
 
+using librbd::util::create_async_context_callback;
 using librbd::util::create_context_callback;
 
 template <typename I>
@@ -30,7 +36,7 @@ void PromoteRequest<I>::send() {
 template <typename I>
 void PromoteRequest<I>::refresh_image() {
   if (!m_image_ctx->state->is_refresh_required()) {
-    create_snapshot();
+    create_promote_snapshot();
     return;
   }
 
@@ -53,16 +59,262 @@ void PromoteRequest<I>::handle_refresh_image(int r) {
     return;
   }
 
-  create_snapshot();
+  if (!util::can_create_primary_snapshot(m_image_ctx, false, m_force,
+                                         &m_rollback_snap_id)) {
+    lderr(cct) << "cannot promote" << dendl;
+    finish(-EINVAL);
+    return;
+  } else if (m_rollback_snap_id == CEPH_NOSNAP) {
+    create_promote_snapshot();
+    return;
+  }
+
+  create_orphan_snapshot();
+}
+
+template <typename I>
+void PromoteRequest<I>::create_orphan_snapshot() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  auto ctx = create_context_callback<
+    PromoteRequest<I>,
+    &PromoteRequest<I>::handle_create_orphan_snapshot>(this);
+
+  auto req = CreateNonPrimaryRequest<I>::create(m_image_ctx, "", CEPH_NOSNAP,
+                                                {}, nullptr, ctx);
+  req->send();
+}
+
+template <typename I>
+void PromoteRequest<I>::handle_create_orphan_snapshot(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to create orphan snapshot: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  list_watchers();
+}
+
+template <typename I>
+void PromoteRequest<I>::list_watchers() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  auto ctx = create_context_callback<
+    PromoteRequest<I>,
+    &PromoteRequest<I>::handle_list_watchers>(this);
+
+  m_watchers.clear();
+  auto flags = librbd::image::LIST_WATCHERS_FILTER_OUT_MY_INSTANCE |
+               librbd::image::LIST_WATCHERS_MIRROR_INSTANCES_ONLY;
+  auto req = librbd::image::ListWatchersRequest<I>::create(
+    *m_image_ctx, flags, &m_watchers, ctx);
+  req->send();
+}
+
+template <typename I>
+void PromoteRequest<I>::handle_list_watchers(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to list watchers: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  if (m_watchers.empty()) {
+    acquire_exclusive_lock();
+    return;
+  }
+
+  wait_update_notify();
 }
 
 template <typename I>
-void PromoteRequest<I>::create_snapshot() {
+void PromoteRequest<I>::wait_update_notify() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  ImageCtx::get_timer_instance(cct, &m_timer, &m_timer_lock);
+
+  std::lock_guard timer_lock{*m_timer_lock};
+
+  m_scheduler_ticks = 5;
+
+  int r = m_image_ctx->state->register_update_watcher(&m_update_watch_ctx,
+                                                      &m_update_watcher_handle);
+  if (r < 0) {
+    lderr(cct) << "failed to register update watcher: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+  
+  scheduler_unregister_update_watcher();
+}
+
+template <typename I>
+void PromoteRequest<I>::handle_update_notify() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  std::lock_guard timer_lock{*m_timer_lock};
+  m_scheduler_ticks = 0;
+}
+
+template <typename I>
+void PromoteRequest<I>::scheduler_unregister_update_watcher() {
+  ceph_assert(ceph_mutex_is_locked(*m_timer_lock));
+
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "scheduler_ticks=" << m_scheduler_ticks << dendl;
+
+  if (m_scheduler_ticks > 0) {
+    m_scheduler_ticks--;
+    m_timer->add_event_after(1, new LambdaContext([this](int) {
+        scheduler_unregister_update_watcher();
+      }));
+    return;
+  }
+
+  m_image_ctx->op_work_queue->queue(new LambdaContext([this](int) {
+      unregister_update_watcher();
+    }), 0);
+}
+
+template <typename I>
+void PromoteRequest<I>::unregister_update_watcher() {
   CephContext *cct = m_image_ctx->cct;
   ldout(cct, 20) << dendl;
 
   auto ctx = create_context_callback<
-    PromoteRequest<I>, &PromoteRequest<I>::handle_create_snapshot>(this);
+    PromoteRequest<I>,
+    &PromoteRequest<I>::handle_unregister_update_watcher>(this);
+
+  m_image_ctx->state->unregister_update_watcher(m_update_watcher_handle, ctx);
+}
+
+template <typename I>
+void PromoteRequest<I>::handle_unregister_update_watcher(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to unregister update watcher: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  list_watchers();
+}
+
+template <typename I>
+void PromoteRequest<I>::acquire_exclusive_lock() {
+  {
+    std::unique_lock locker{m_image_ctx->owner_lock};
+    if (m_image_ctx->exclusive_lock != nullptr &&
+        !m_image_ctx->exclusive_lock->is_lock_owner()) {
+      CephContext *cct = m_image_ctx->cct;
+      ldout(cct, 20) << dendl;
+
+      m_lock_acquired = true;
+      m_image_ctx->exclusive_lock->block_requests(0);
+
+      auto ctx = create_context_callback<
+        PromoteRequest<I>,
+        &PromoteRequest<I>::handle_acquire_exclusive_lock>(this);
+
+      m_image_ctx->exclusive_lock->acquire_lock(ctx);
+      return;
+    }
+  }
+
+  rollback();
+}
+
+template <typename I>
+void PromoteRequest<I>::handle_acquire_exclusive_lock(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to acquire exclusive lock: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  } else {
+    std::unique_lock locker{m_image_ctx->owner_lock};
+    if (m_image_ctx->exclusive_lock != nullptr &&
+        !m_image_ctx->exclusive_lock->is_lock_owner()) {
+      lderr(cct) << "failed to acquire exclusive lock" << dendl;
+      r = m_image_ctx->exclusive_lock->get_unlocked_op_error();
+      locker.unlock();
+      finish(r);
+    }
+  }
+
+  rollback();
+}
+
+template <typename I>
+void PromoteRequest<I>::rollback() {
+  if (m_rollback_snap_id == CEPH_NOSNAP) {
+    create_promote_snapshot();
+    return;
+  }
+
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  std::shared_lock owner_locker{m_image_ctx->owner_lock};
+  std::shared_lock image_locker{m_image_ctx->image_lock};
+
+  auto info = m_image_ctx->get_snap_info(m_rollback_snap_id);
+  ceph_assert(info != nullptr);
+  auto snap_namespace = info->snap_namespace;
+  auto snap_name = info->name;
+
+  image_locker.unlock();
+
+  auto ctx = create_async_context_callback(
+    *m_image_ctx, create_context_callback<
+      PromoteRequest<I>, &PromoteRequest<I>::handle_rollback>(this));
+
+  m_image_ctx->operations->execute_snap_rollback(snap_namespace, snap_name,
+                                                 m_progress_ctx, ctx);
+}
+
+template <typename I>
+void PromoteRequest<I>::handle_rollback(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to rollback: " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  create_promote_snapshot();
+}
+
+template <typename I>
+void PromoteRequest<I>::create_promote_snapshot() {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << dendl;
+
+  auto ctx = create_context_callback<
+    PromoteRequest<I>,
+    &PromoteRequest<I>::handle_create_promote_snapshot>(this);
 
   auto req = CreatePrimaryRequest<I>::create(m_image_ctx, false, true, nullptr,
                                              ctx);
@@ -70,12 +322,49 @@ void PromoteRequest<I>::create_snapshot() {
 }
 
 template <typename I>
-void PromoteRequest<I>::handle_create_snapshot(int r) {
+void PromoteRequest<I>::handle_create_promote_snapshot(int r) {
+  CephContext *cct = m_image_ctx->cct;
+  ldout(cct, 20) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "failed to create promote snapshot: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  release_exclusive_lock();
+}
+
+template <typename I>
+void PromoteRequest<I>::release_exclusive_lock() {
+  if (m_lock_acquired) {
+    std::unique_lock locker{m_image_ctx->owner_lock};
+    if (m_image_ctx->exclusive_lock != nullptr) {
+      CephContext *cct = m_image_ctx->cct;
+      ldout(cct, 20) << dendl;
+
+      m_image_ctx->exclusive_lock->unblock_requests();
+
+      auto ctx = create_context_callback<
+        PromoteRequest<I>,
+        &PromoteRequest<I>::handle_release_exclusive_lock>(this);
+
+      m_image_ctx->exclusive_lock->release_lock(ctx);
+      return;
+    }
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void PromoteRequest<I>::handle_release_exclusive_lock(int r) {
   CephContext *cct = m_image_ctx->cct;
   ldout(cct, 20) << "r=" << r << dendl;
 
   if (r < 0) {
-    lderr(cct) << "failed to create mirror snapshot: " << cpp_strerror(r)
+    lderr(cct) << "failed to release exclusive lock: " << cpp_strerror(r)
                << dendl;
     finish(r);
     return;
index 7efd921d8d17bc9d9fd62fcd6795cf0c05020fff..f6eb92b61da225effadf3843686be762f5374613 100644 (file)
@@ -5,10 +5,13 @@
 #define CEPH_LIBRBD_MIRROR_SNAPSHOT_PROMOTE_REQUEST_H
 
 #include "include/buffer.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/internal.h"
 
 #include <string>
 #include <set>
 
+class SafeTimer;
 struct Context;
 
 namespace librbd {
@@ -21,12 +24,13 @@ namespace snapshot {
 template <typename ImageCtxT = librbd::ImageCtx>
 class PromoteRequest {
 public:
-  static PromoteRequest *create(ImageCtxT *image_ctx, Context *on_finish) {
-    return new PromoteRequest(image_ctx, on_finish);
+  static PromoteRequest *create(ImageCtxT *image_ctx, bool force,
+                                Context *on_finish) {
+    return new PromoteRequest(image_ctx, force, on_finish);
   }
 
-  PromoteRequest(ImageCtxT *image_ctx, Context *on_finish)
-    : m_image_ctx(image_ctx), m_on_finish(on_finish) {
+  PromoteRequest(ImageCtxT *image_ctx, bool force, Context *on_finish)
+    : m_image_ctx(image_ctx), m_force(force), m_on_finish(on_finish) {
   }
 
   void send();
@@ -37,11 +41,29 @@ private:
    *
    * <start>
    *    |
-   *    v
-   * REFRESH_IMAGE
+   *    v          (can promote)
+   * REFRESH_IMAGE -------------------------------\
+   *    |                                         |
+   *    | (needs rollback)                        |
+   *    v                                         |
+   * CREATE_ORPHAN_SNAPSHOT                       |
+   *    |                                         |
+   *    |     /-- UNREGISTER_UPDATE_WATCHER <-\   |
+   *    v     v                               |   |
+   * LIST_WATCHERS ----> WAIT_UPDATE_NOTIFY --/   |
+   *    |                                         |
+   *    | (no watchers)                           |
+   *    v                                         |
+   * ACQUIRE_EXCLUSIVE_LOCK                       |
+   *    |  (skip if not needed)                   |
+   *    v                                         |
+   * ROLLBACK                                     |
+   *    |                                         |
+   *    v                                         |
+   * CREATE_PROMOTE_SNAPSHOT <--------------------/
    *    |
    *    v
-   * CREATE_SNAPSHOT
+   * RELEASE_EXCLUSIVE_LOCK (skip if not needed)
    *    |
    *    v
    * <finish>
@@ -50,13 +72,61 @@ private:
    */
 
   ImageCtxT *m_image_ctx;
+  bool m_force;
   Context *m_on_finish;
 
+  uint64_t m_rollback_snap_id = CEPH_NOSNAP;
+  bool m_lock_acquired = false;
+  NoOpProgressContext m_progress_ctx;
+
+  class UpdateWatchCtx : public librbd::UpdateWatchCtx {
+  public:
+    UpdateWatchCtx(PromoteRequest *promote_request)
+      : promote_request(promote_request) {
+    }
+
+    void handle_notify() {
+      promote_request->handle_update_notify();
+    }
+
+  private:
+    PromoteRequest *promote_request;
+
+  } m_update_watch_ctx = {this};
+
+  std::list<obj_watch_t> m_watchers;
+  uint64_t m_update_watcher_handle = 0;
+  uint64_t m_scheduler_ticks = 0;
+  SafeTimer *m_timer = nullptr;
+  ceph::mutex *m_timer_lock = nullptr;
+
   void refresh_image();
   void handle_refresh_image(int r);
 
-  void create_snapshot();
-  void handle_create_snapshot(int r);
+  void create_orphan_snapshot();
+  void handle_create_orphan_snapshot(int r);
+
+  void list_watchers();
+  void handle_list_watchers(int r);
+
+  void wait_update_notify();
+  void handle_update_notify();
+  void scheduler_unregister_update_watcher();
+
+  void unregister_update_watcher();
+  void handle_unregister_update_watcher(int r);
+
+  void acquire_exclusive_lock();
+  void handle_acquire_exclusive_lock(int r);
+
+  void rollback();
+  void handle_rollback(int r);
+
+  void create_promote_snapshot();
+  void handle_create_promote_snapshot(int r);
+
+  void release_exclusive_lock();
+  void handle_release_exclusive_lock(int r);
 
   void finish(int r);
 
index 19cc88bfb2e4b97fb85abd50501e3d73bcdb564b..b0c68237e87e4453430673f35e0f2da31ff2464f 100644 (file)
@@ -87,6 +87,7 @@ set(unittest_librbd_srcs
   managed_lock/test_mock_ReleaseRequest.cc
   mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
   mirror/snapshot/test_mock_CreatePrimaryRequest.cc
+  mirror/snapshot/test_mock_PromoteRequest.cc
   mirror/snapshot/test_mock_UnlinkPeerRequest.cc
   mirror/snapshot/test_mock_Utils.cc
   mirror/test_mock_DisableRequest.cc
index 97b6a22fea346384b315da5adebceb6286982eaf..08e55662ecd659661ee2c941814f3289cf6b1490 100644 (file)
@@ -27,6 +27,8 @@ namespace mirror {
 namespace snapshot {
 namespace util {
 
+namespace {
+
 struct Mock {
   static Mock* s_instance;
 
@@ -40,6 +42,8 @@ struct Mock {
 
 Mock *Mock::s_instance = nullptr;
 
+} // anonymous namespace
+
 template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx,
                                             bool demoted, bool force,
                                             uint64_t *rollback_snap_id) {
diff --git a/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc
new file mode 100644 (file)
index 0000000..304ad62
--- /dev/null
@@ -0,0 +1,374 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/stringify.h"
+#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 "librbd/image/ListWatchersRequest.h"
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h"
+#include "librbd/mirror/snapshot/CreatePrimaryRequest.h"
+#include "librbd/mirror/snapshot/PromoteRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+  explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+  }
+};
+
+} // anonymous namespace
+
+namespace image {
+
+template <>
+struct ListWatchersRequest<MockTestImageCtx> {
+  std::list<obj_watch_t> *watchers;
+  Context* on_finish = nullptr;
+  static ListWatchersRequest* s_instance;
+  static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags,
+                                     std::list<obj_watch_t> *watchers,
+                                     Context *on_finish) {
+    ceph_assert(s_instance != nullptr);
+    s_instance->watchers = watchers;
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  MOCK_METHOD0(send, void());
+
+  ListWatchersRequest() {
+    s_instance = this;
+  }
+};
+
+ListWatchersRequest<MockTestImageCtx>* ListWatchersRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image
+
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+namespace {
+
+struct Mock {
+  static Mock* s_instance;
+
+  Mock() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD4(can_create_primary_snapshot,
+               bool(librbd::MockTestImageCtx *, bool, bool, uint64_t *));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx,
+                                            bool demoted, bool force,
+                                            uint64_t *rollback_snap_id) {
+  return Mock::s_instance->can_create_primary_snapshot(image_ctx, demoted,
+                                                       force, rollback_snap_id);
+}
+
+} // namespace util
+
+template <>
+struct CreateNonPrimaryRequest<MockTestImageCtx> {
+  std::string primary_mirror_uuid;
+  uint64_t primary_snap_id = CEPH_NOSNAP;
+  Context* on_finish = nullptr;
+  static CreateNonPrimaryRequest* s_instance;
+  static CreateNonPrimaryRequest *create(MockTestImageCtx *image_ctx,
+                                         const std::string &primary_mirror_uuid,
+                                         uint64_t primary_snap_id,
+                                         const ImageState &image_state,
+                                         uint64_t *snap_id,
+                                         Context *on_finish) {
+    ceph_assert(s_instance != nullptr);
+    s_instance->primary_mirror_uuid = primary_mirror_uuid;
+    s_instance->primary_snap_id = primary_snap_id;
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  MOCK_METHOD0(send, void());
+
+  CreateNonPrimaryRequest() {
+    s_instance = this;
+  }
+};
+
+CreateNonPrimaryRequest<MockTestImageCtx>* CreateNonPrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+template <>
+struct CreatePrimaryRequest<MockTestImageCtx> {
+  bool demoted = false;
+  bool force = false;
+  Context* on_finish = nullptr;
+  static CreatePrimaryRequest* s_instance;
+  static CreatePrimaryRequest *create(MockTestImageCtx *image_ctx, bool demoted,
+                                      bool force, uint64_t *snap_id,
+                                      Context *on_finish) {
+    ceph_assert(s_instance != nullptr);
+    s_instance->demoted = demoted;
+    s_instance->force = force;
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  MOCK_METHOD0(send, void());
+
+  CreatePrimaryRequest() {
+    s_instance = this;
+  }
+};
+
+CreatePrimaryRequest<MockTestImageCtx>* CreatePrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/PromoteRequest.cc"
+template class librbd::mirror::snapshot::PromoteRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotPromoteRequest : public TestMockFixture {
+public:
+  typedef librbd::image::ListWatchersRequest<MockTestImageCtx> MockListWatchersRequest;
+  typedef PromoteRequest<MockTestImageCtx> MockPromoteRequest;
+  typedef CreateNonPrimaryRequest<MockTestImageCtx> MockCreateNonPrimaryRequest;
+  typedef CreatePrimaryRequest<MockTestImageCtx> MockCreatePrimaryRequest;
+  typedef util::Mock MockUtils;
+
+  void expect_refresh_image(MockTestImageCtx &mock_image_ctx,
+                            bool refresh_required, int r) {
+    EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+      .WillOnce(Return(refresh_required));
+    if (refresh_required) {
+      EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+        .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+    }
+  }
+
+  void expect_can_create_primary_snapshot(MockUtils &mock_utils, bool force,
+                                          uint64_t rollback_snap_id,
+                                          bool result) {
+    EXPECT_CALL(mock_utils,
+                can_create_primary_snapshot(_, false, force, _))
+      .WillOnce(DoAll(
+                  WithArg<3>(Invoke([rollback_snap_id](uint64_t *snap_id) {
+                                      *snap_id = rollback_snap_id;
+                                    })),
+                  Return(result)));
+  }
+
+  void expect_create_orphan_snapshot(
+      MockTestImageCtx &mock_image_ctx,
+      MockCreateNonPrimaryRequest &mock_create_non_primary_request, int r) {
+    EXPECT_CALL(mock_create_non_primary_request, send())
+      .WillOnce(
+        Invoke([&mock_image_ctx, &mock_create_non_primary_request, r]() {
+                 mock_image_ctx.image_ctx->op_work_queue->queue(
+                   mock_create_non_primary_request.on_finish, r);
+               }));
+  }
+
+  void expect_list_watchers(
+      MockTestImageCtx &mock_image_ctx,
+      MockListWatchersRequest &mock_list_watchers_request,
+      const std::list<obj_watch_t> &watchers, int r) {
+    EXPECT_CALL(mock_list_watchers_request, send())
+      .WillOnce(
+        Invoke([&mock_image_ctx, &mock_list_watchers_request, watchers, r]() {
+                 *mock_list_watchers_request.watchers = watchers;
+                 mock_image_ctx.image_ctx->op_work_queue->queue(
+                   mock_list_watchers_request.on_finish, r);
+               }));
+  }
+
+  void expect_acquire_lock(MockTestImageCtx &mock_image_ctx, int r) {
+    if (mock_image_ctx.exclusive_lock == nullptr) {
+      return;
+    }
+    EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner())
+      .WillOnce(Return(false));
+    EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(_));
+    EXPECT_CALL(*mock_image_ctx.exclusive_lock, acquire_lock(_))
+      .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+    if (r == 0) {
+      EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner())
+        .WillOnce(Return(true));
+    }
+  }
+
+  void expect_get_snap_info(MockTestImageCtx &mock_image_ctx,
+                            uint64_t snap_id, const SnapInfo* snap_info) {
+    EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id))
+      .WillOnce(Return(snap_info));
+  }
+
+  void expect_rollback(MockTestImageCtx &mock_image_ctx, uint64_t snap_id,
+                       const SnapInfo* snap_info, int r) {
+    expect_get_snap_info(mock_image_ctx, snap_id, snap_info);
+    EXPECT_CALL(*mock_image_ctx.operations,
+                execute_snap_rollback(snap_info->snap_namespace,
+                                      snap_info->name, _, _))
+      .WillOnce(WithArg<3>(CompleteContext(
+                             r, mock_image_ctx.image_ctx->op_work_queue)));
+  }
+
+  void expect_create_promote_snapshot(
+      MockTestImageCtx &mock_image_ctx,
+      MockCreatePrimaryRequest &mock_create_primary_request, int r) {
+    EXPECT_CALL(mock_create_primary_request, send())
+      .WillOnce(
+        Invoke([&mock_image_ctx, &mock_create_primary_request, r]() {
+                 mock_image_ctx.image_ctx->op_work_queue->queue(
+                   mock_create_primary_request.on_finish, r);
+               }));
+  }
+
+  void expect_release_lock(MockTestImageCtx &mock_image_ctx, int r) {
+    if (mock_image_ctx.exclusive_lock == nullptr) {
+      return;
+    }
+    EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests());
+    EXPECT_CALL(*mock_image_ctx.exclusive_lock, release_lock(_))
+      .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+  }
+};
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, Success) {
+  REQUIRE_FORMAT_V2();
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+
+  const bool force = false;
+
+  InSequence seq;
+
+  expect_refresh_image(mock_image_ctx, true, 0);
+  MockUtils mock_utils;
+  expect_can_create_primary_snapshot(mock_utils, force, CEPH_NOSNAP, true);
+  MockCreatePrimaryRequest mock_create_primary_request;
+  expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+                                 0);
+  C_SaferCond ctx;
+  auto req = new MockPromoteRequest(&mock_image_ctx, false, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, SuccessRollback) {
+  REQUIRE_FORMAT_V2();
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_op_work_queue(mock_image_ctx);
+
+  MockExclusiveLock mock_exclusive_lock;
+  if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+  }
+
+  const bool force = true;
+
+  InSequence seq;
+
+  expect_refresh_image(mock_image_ctx, true, 0);
+  MockUtils mock_utils;
+  expect_can_create_primary_snapshot(mock_utils, force, 123, true);
+  MockCreateNonPrimaryRequest mock_create_non_primary_request;
+  expect_create_orphan_snapshot(mock_image_ctx, mock_create_non_primary_request,
+                                0);
+  MockListWatchersRequest mock_list_watchers_request;
+  expect_list_watchers(mock_image_ctx, mock_list_watchers_request, {}, 0);
+  expect_acquire_lock(mock_image_ctx, 0);
+  
+  SnapInfo snap_info = {"snap", cls::rbd::MirrorPrimarySnapshotNamespace{}, 0,
+                        {}, 0, 0, {}};
+  expect_rollback(mock_image_ctx, 123, &snap_info, 0);
+  MockCreatePrimaryRequest mock_create_primary_request;
+  expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+                                 0);
+  expect_release_lock(mock_image_ctx, 0);
+
+  C_SaferCond ctx;
+  auto req = new MockPromoteRequest(&mock_image_ctx, force, &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, RefreshError) {
+  REQUIRE_FORMAT_V2();
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+
+  InSequence seq;
+
+  expect_refresh_image(mock_image_ctx, true, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = new MockPromoteRequest(&mock_image_ctx, false, &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, ErrorCannotRollback) {
+  REQUIRE_FORMAT_V2();
+
+  librbd::ImageCtx *ictx;
+  ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+  MockTestImageCtx mock_image_ctx(*ictx);
+  expect_op_work_queue(mock_image_ctx);
+
+  const bool force = false;
+
+  InSequence seq;
+
+  expect_refresh_image(mock_image_ctx, true, 0);
+  MockUtils mock_utils;
+  expect_can_create_primary_snapshot(mock_utils, force, CEPH_NOSNAP, false);
+
+  C_SaferCond ctx;
+  auto req = new MockPromoteRequest(&mock_image_ctx, force, &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
index a817d333e1d5f5434678e803c14300b365628eac..b9c8c8d07590b02c3016a984b1fea95b0d742402 100644 (file)
@@ -12,6 +12,8 @@ class Context;
 
 namespace librbd {
 
+class UpdateWatchCtx;
+
 struct MockImageState {
   MOCK_CONST_METHOD0(is_refresh_required, bool());
   MOCK_METHOD1(refresh, void(Context*));
@@ -25,6 +27,9 @@ struct MockImageState {
 
   MOCK_METHOD1(prepare_lock, void(Context*));
   MOCK_METHOD0(handle_prepare_lock_complete, void());
+
+  MOCK_METHOD2(register_update_watcher, int(UpdateWatchCtx *, uint64_t *));
+  MOCK_METHOD2(unregister_update_watcher, void(uint64_t, Context *));  
 };
 
 } // namespace librbd
index b085a11f138c8df3246010aa318d1de20bcdcd89..17f9cef43fb8c21ebd2b48f2ba8f2a3f635a9c4e 100644 (file)
@@ -1396,3 +1396,39 @@ TEST_F(TestMirroring, SnapshotImageState)
   ASSERT_EQ(0, image.close());
   ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
 }
+
+TEST_F(TestMirroring, SnapshotPromoteDemote)
+{
+  REQUIRE_FORMAT_V2();
+
+  ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_IMAGE));
+  std::string peer_uuid;
+  ASSERT_EQ(0, m_rbd.mirror_peer_site_add(m_ioctx, &peer_uuid,
+                                          RBD_MIRROR_PEER_DIRECTION_RX_TX,
+                                          "cluster", "client"));
+
+  uint64_t features;
+  ASSERT_TRUE(get_features(&features));
+  features &= ~RBD_FEATURE_JOURNALING;
+  int order = 20;
+  ASSERT_EQ(0, m_rbd.create2(m_ioctx, image_name.c_str(), 4096, features,
+                             &order));
+
+  librbd::Image image;
+  ASSERT_EQ(0, m_rbd.open(m_ioctx, image, image_name.c_str()));
+  ASSERT_EQ(0, image.mirror_image_enable());
+  librbd::mirror_image_mode_t mode;
+  ASSERT_EQ(0, image.mirror_image_get_mode(&mode));
+  ASSERT_EQ(RBD_MIRROR_IMAGE_MODE_SNAPSHOT, mode);
+
+  ASSERT_EQ(-EINVAL, image.mirror_image_promote(false));
+  ASSERT_EQ(0, image.mirror_image_demote());
+  ASSERT_EQ(0, image.mirror_image_promote(false));
+  ASSERT_EQ(0, image.mirror_image_demote());
+  ASSERT_EQ(0, image.mirror_image_promote(false));
+
+  ASSERT_EQ(0, image.close());
+  ASSERT_EQ(0, m_rbd.remove(m_ioctx, image_name.c_str()));
+  ASSERT_EQ(0, m_rbd.mirror_peer_site_remove(m_ioctx, peer_uuid));
+  ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+}