]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd: split image pre-removal logic to separate state machine
authorJason Dillaman <dillaman@redhat.com>
Thu, 21 Feb 2019 03:05:51 +0000 (22:05 -0500)
committerJason Dillaman <dillaman@redhat.com>
Thu, 21 Feb 2019 03:05:51 +0000 (22:05 -0500)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/librbd/CMakeLists.txt
src/librbd/image/PreRemoveRequest.cc [new file with mode: 0644]
src/librbd/image/PreRemoveRequest.h [new file with mode: 0644]
src/librbd/image/RemoveRequest.cc
src/librbd/image/RemoveRequest.h
src/test/librbd/CMakeLists.txt
src/test/librbd/image/test_mock_PreRemoveRequest.cc [new file with mode: 0644]
src/test/librbd/image/test_mock_RemoveRequest.cc

index e7da59cf267f6b24ca61d0f45f74c404dae3c490..a3440a1b24e558d619a2d0e411f968a6bb01b367 100644 (file)
@@ -59,6 +59,7 @@ set(librbd_internal_srcs
   image/DetachParentRequest.cc
   image/ListWatchersRequest.cc
   image/OpenRequest.cc
+  image/PreRemoveRequest.cc
   image/RefreshParentRequest.cc
   image/RefreshRequest.cc
   image/RemoveRequest.cc
diff --git a/src/librbd/image/PreRemoveRequest.cc b/src/librbd/image/PreRemoveRequest.cc
new file mode 100644 (file)
index 0000000..0ed6cf9
--- /dev/null
@@ -0,0 +1,305 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/image/PreRemoveRequest.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "cls/rbd/cls_rbd_types.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/Utils.h"
+#include "librbd/image/ListWatchersRequest.h"
+#include "librbd/journal/DisabledPolicy.h"
+#include "librbd/operation/SnapshotRemoveRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::image::PreRemoveRequest: " << this \
+                           << " " << __func__ << ": "
+
+namespace librbd {
+namespace image {
+
+namespace {
+
+bool auto_delete_snapshot(const SnapInfo& snap_info) {
+  auto snap_namespace_type = cls::rbd::get_snap_namespace_type(
+    snap_info.snap_namespace);
+  switch (snap_namespace_type) {
+  case cls::rbd::SNAPSHOT_NAMESPACE_TYPE_TRASH:
+    return true;
+  default:
+    return false;
+  }
+}
+
+} // anonymous namespace
+
+using util::create_context_callback;
+using util::create_rados_callback;
+
+template <typename I>
+void PreRemoveRequest<I>::send() {
+  auto cct = m_image_ctx->cct;
+  if (m_image_ctx->operations_disabled) {
+    lderr(cct) << "image operations disabled due to unsupported op features"
+               << dendl;
+    finish(-EROFS);
+    return;
+  }
+
+  acquire_exclusive_lock();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::acquire_exclusive_lock() {
+  RWLock::RLocker owner_lock(m_image_ctx->owner_lock);
+  if (m_image_ctx->exclusive_lock == nullptr) {
+    validate_image_removal();
+    return;
+  }
+
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << dendl;
+
+  // do not attempt to open the journal when removing the image in case
+  // it's corrupt
+  if (m_image_ctx->test_features(RBD_FEATURE_JOURNALING)) {
+    RWLock::WLocker snap_locker(m_image_ctx->snap_lock);
+    m_image_ctx->set_journal_policy(new journal::DisabledPolicy());
+  }
+
+  if (m_force) {
+    auto ctx = create_context_callback<
+      PreRemoveRequest<I>,
+      &PreRemoveRequest<I>::handle_exclusive_lock_force>(this);
+
+    m_exclusive_lock = m_image_ctx->exclusive_lock;
+    m_exclusive_lock->shut_down(ctx);
+  } else {
+    auto ctx = create_context_callback<
+      PreRemoveRequest<I>, &PreRemoveRequest<I>::handle_exclusive_lock>(this);
+
+    m_image_ctx->exclusive_lock->try_acquire_lock(ctx);
+  }
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_exclusive_lock(int r) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  if (r < 0 || !m_image_ctx->exclusive_lock->is_lock_owner()) {
+    lderr(cct) << "cannot obtain exclusive lock - not removing" << dendl;
+    finish(-EBUSY);
+    return;
+  }
+
+  validate_image_removal();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_exclusive_lock_force(int r) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  delete m_exclusive_lock;
+  m_exclusive_lock = nullptr;
+
+  if (r < 0) {
+    lderr(cct) << "error shutting down exclusive lock: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  ceph_assert(m_image_ctx->exclusive_lock == nullptr);
+  validate_image_removal();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::validate_image_removal() {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << dendl;
+
+  if (!m_image_ctx->ignore_migrating &&
+      m_image_ctx->test_features(RBD_FEATURE_MIGRATING)) {
+    lderr(cct) << "image in migration state - not removing" << dendl;
+    finish(-EBUSY);
+    return;
+  }
+
+  check_image_snaps();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::check_image_snaps() {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << dendl;
+
+  m_image_ctx->snap_lock.get_read();
+  for (auto& snap_info : m_image_ctx->snap_info) {
+    if (auto_delete_snapshot(snap_info.second)) {
+      m_snap_infos.insert(snap_info);
+    } else {
+      m_image_ctx->snap_lock.put_read();
+
+      lderr(cct) << "image has snapshots - not removing" << dendl;
+      finish(-ENOTEMPTY);
+      return;
+    }
+  }
+  m_image_ctx->snap_lock.put_read();
+
+  list_image_watchers();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::list_image_watchers() {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << dendl;
+
+  int flags = LIST_WATCHERS_FILTER_OUT_MY_INSTANCE |
+              LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES;
+  auto ctx = create_context_callback<
+    PreRemoveRequest<I>,
+    &PreRemoveRequest<I>::handle_list_image_watchers>(this);
+  auto req = ListWatchersRequest<I>::create(*m_image_ctx, flags, &m_watchers,
+                                            ctx);
+  req->send();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_list_image_watchers(int r) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  if (r < 0) {
+    lderr(cct) << "error listing image watchers: " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  check_image_watchers();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::check_image_watchers() {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << dendl;
+
+  if (!m_watchers.empty()) {
+    lderr(cct) << "image has watchers - not removing" << dendl;
+    finish(-EBUSY);
+    return;
+  }
+
+  check_group();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::check_group() {
+  if (m_image_ctx->old_format) {
+    finish(0);
+    return;
+  }
+
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << dendl;
+
+  librados::ObjectReadOperation op;
+  librbd::cls_client::image_group_get_start(&op);
+
+  auto rados_completion = create_rados_callback<
+    PreRemoveRequest<I>, &PreRemoveRequest<I>::handle_check_group>(this);
+  m_out_bl.clear();
+  int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid,
+                                          rados_completion, &op, &m_out_bl);
+  ceph_assert(r == 0);
+  rados_completion->release();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_check_group(int r) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  cls::rbd::GroupSpec s;
+  if (r == 0) {
+    auto it = m_out_bl.cbegin();
+    r = librbd::cls_client::image_group_get_finish(&it, &s);
+  }
+  if (r < 0 && r != -EOPNOTSUPP) {
+    lderr(cct) << "error fetching group for image: " << cpp_strerror(r)
+               << dendl;
+    finish(r);
+    return;
+  }
+
+  if (s.is_valid()) {
+    lderr(cct) << "image is in a group - not removing" << dendl;
+    finish(-EMLINK);
+    return;
+  }
+
+  remove_snapshot();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::remove_snapshot() {
+  if (m_snap_infos.empty()) {
+    finish(0);
+    return;
+  }
+
+  auto cct = m_image_ctx->cct;
+  auto snap_id = m_snap_infos.begin()->first;
+  auto& snap_info = m_snap_infos.begin()->second;
+  ldout(cct, 20) << "snap_id=" << snap_id << ", "
+                 << "snap_name=" << snap_info.name << dendl;
+
+  RWLock::RLocker owner_lock(m_image_ctx->owner_lock);
+  auto ctx = create_context_callback<
+    PreRemoveRequest<I>, &PreRemoveRequest<I>::handle_remove_snapshot>(this);
+  auto req = librbd::operation::SnapshotRemoveRequest<I>::create(
+    *m_image_ctx, snap_info.snap_namespace, snap_info.name,
+    snap_id, ctx);
+  req->send();
+
+}
+
+template <typename I>
+void PreRemoveRequest<I>::handle_remove_snapshot(int r) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  if (r < 0 && r != -ENOENT) {
+    auto snap_id = m_snap_infos.begin()->first;
+    lderr(cct) << "failed to auto-prune snapshot " << snap_id << ": "
+               << cpp_strerror(r) << dendl;
+
+    if (r == -EBUSY) {
+      r = -ENOTEMPTY;
+    }
+    finish(r);
+    return;
+  }
+
+  ceph_assert(!m_snap_infos.empty());
+  m_snap_infos.erase(m_snap_infos.begin());
+
+  remove_snapshot();
+}
+
+template <typename I>
+void PreRemoveRequest<I>::finish(int r) {
+  auto cct = m_image_ctx->cct;
+  ldout(cct, 5) << "r=" << r << dendl;
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace image
+} // namespace librbd
+
+template class librbd::image::PreRemoveRequest<librbd::ImageCtx>;
diff --git a/src/librbd/image/PreRemoveRequest.h b/src/librbd/image/PreRemoveRequest.h
new file mode 100644 (file)
index 0000000..9998149
--- /dev/null
@@ -0,0 +1,100 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_IMAGE_PRE_REMOVE_REQUEST_H
+#define CEPH_LIBRBD_IMAGE_PRE_REMOVE_REQUEST_H
+
+#include "include/rados/librados.hpp"
+#include "include/buffer.h"
+#include "librbd/ImageCtx.h"
+#include <list>
+#include <map>
+
+class Context;
+
+namespace librbd {
+namespace image {
+
+template <typename ImageCtxT>
+class PreRemoveRequest {
+public:
+
+  static PreRemoveRequest *create(ImageCtxT *image_ctx, bool force,
+                                  Context *on_finish) {
+    return new PreRemoveRequest(image_ctx, force, on_finish);
+  }
+
+  PreRemoveRequest(ImageCtxT *image_ctx, bool force, Context *on_finish)
+    : m_image_ctx(image_ctx), m_force(force), m_on_finish(on_finish) {
+  }
+
+  void send();
+
+private:
+  /**
+   * @verbatim
+   *
+   *       <start>
+   *          |
+   *          v
+   *   CHECK EXCLUSIVE LOCK
+   *          |
+   *          v (skip if not needed)
+   *  ACQUIRE EXCLUSIVE LOCK
+   *          |
+   *          v
+   *   CHECK IMAGE WATCHERS
+   *          |
+   *          v
+   *     CHECK GROUP
+   *          |
+   *          |   /------\
+   *          |   |      |
+   *          v   v      |
+   *    REMOVE SNAPS ----/
+   *          |
+   *          v
+   *      <finish>
+   *
+   * @endverbatim
+   */
+
+  ImageCtxT* m_image_ctx;
+  bool m_force;
+  Context* m_on_finish;
+
+  decltype(m_image_ctx->exclusive_lock) m_exclusive_lock = nullptr;
+
+  bufferlist m_out_bl;
+  std::list<obj_watch_t> m_watchers;
+
+  std::map<uint64_t, SnapInfo> m_snap_infos;
+
+  void acquire_exclusive_lock();
+  void handle_exclusive_lock(int r);
+  void handle_exclusive_lock_force(int r);
+
+  void validate_image_removal();
+  void check_image_snaps();
+
+  void list_image_watchers();
+  void handle_list_image_watchers(int r);
+
+  void check_image_watchers();
+
+  void check_group();
+  void handle_check_group(int r);
+
+  void remove_snapshot();
+  void handle_remove_snapshot(int r);
+
+  void finish(int r);
+
+};
+
+} // namespace image
+} // namespace librbd
+
+extern template class librbd::image::PreRemoveRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_PRE_REMOVE_REQUEST_H
index dad354bfd790694f1eec20243fbc5bb3fdc000d7..88af466615a5e24b7bc1c15a14f06c175d0a9952 100644 (file)
@@ -8,14 +8,11 @@
 #include "librbd/ImageState.h"
 #include "librbd/Journal.h"
 #include "librbd/ObjectMap.h"
-#include "librbd/ExclusiveLock.h"
 #include "librbd/MirroringWatcher.h"
 #include "librbd/image/DetachChildRequest.h"
-#include "librbd/image/ListWatchersRequest.h"
-#include "librbd/journal/DisabledPolicy.h"
+#include "librbd/image/PreRemoveRequest.h"
 #include "librbd/journal/RemoveRequest.h"
 #include "librbd/mirror/DisableRequest.h"
-#include "librbd/operation/SnapshotRemoveRequest.h"
 #include "librbd/operation/TrimRequest.h"
 
 #define dout_subsys ceph_subsys_rbd
 namespace librbd {
 namespace image {
 
-namespace {
-
-bool auto_delete_snapshot(const SnapInfo& snap_info) {
-  auto snap_namespace_type = cls::rbd::get_snap_namespace_type(
-    snap_info.snap_namespace);
-  switch (snap_namespace_type) {
-  case cls::rbd::SNAPSHOT_NAMESPACE_TYPE_TRASH:
-    return true;
-  default:
-    return false;
-  }
-}
-
-} // anonymous namespace
-
 using librados::IoCtx;
 using util::create_context_callback;
 using util::create_async_context_callback;
@@ -82,7 +64,7 @@ void RemoveRequest<I>::send() {
 template<typename I>
 void RemoveRequest<I>::open_image() {
   if (m_image_ctx != nullptr) {
-    check_exclusive_lock();
+    pre_remove_image();
     return;
   }
 
@@ -122,246 +104,29 @@ void RemoveRequest<I>::handle_open_image(int r) {
   m_old_format = m_image_ctx->old_format;
   m_unknown_format = false;
 
-  check_exclusive_lock();
-}
-
-template<typename I>
-void RemoveRequest<I>::check_exclusive_lock() {
-  if (m_image_ctx->operations_disabled) {
-    lderr(m_cct) << "image operations disabled due to unsupported op features"
-                 << dendl;
-    send_close_image(-EROFS);
-    return;
-  }
-
-  ldout(m_cct, 20) << dendl;
-
-  if (m_image_ctx->exclusive_lock == nullptr) {
-    validate_image_removal();
-  } else {
-    acquire_exclusive_lock();
-  }
-}
-
-template<typename I>
-void RemoveRequest<I>::acquire_exclusive_lock() {
-  ldout(m_cct, 20) << dendl;
-
-  // do not attempt to open the journal when removing the image in case
-  // it's corrupt
-  if (m_image_ctx->test_features(RBD_FEATURE_JOURNALING)) {
-    RWLock::WLocker snap_locker(m_image_ctx->snap_lock);
-    m_image_ctx->set_journal_policy(new journal::DisabledPolicy());
-  }
-
-  using klass = RemoveRequest<I>;
-  if (m_force) {
-    Context *ctx = create_context_callback<
-      klass, &klass::handle_exclusive_lock_force>(this);
-    m_exclusive_lock = m_image_ctx->exclusive_lock;
-    m_exclusive_lock->shut_down(ctx);
-  } else {
-    Context *ctx = create_context_callback<
-      klass, &klass::handle_exclusive_lock>(this);
-    RWLock::WLocker owner_lock(m_image_ctx->owner_lock);
-    m_image_ctx->exclusive_lock->try_acquire_lock(ctx);
-  }
-}
-
-template<typename I>
-void RemoveRequest<I>::handle_exclusive_lock_force(int r) {
-  ldout(m_cct, 20) << "r=" << r << dendl;
-
-  delete m_exclusive_lock;
-  m_exclusive_lock = nullptr;
-
-  if (r < 0) {
-    lderr(m_cct) << "error shutting down exclusive lock: "
-                 << cpp_strerror(r) << dendl;
-    send_close_image(r);
-    return;
-  }
-
-  ceph_assert(m_image_ctx->exclusive_lock == nullptr);
-  validate_image_removal();
+  pre_remove_image();
 }
 
 template<typename I>
-void RemoveRequest<I>::handle_exclusive_lock(int r) {
-  ldout(m_cct, 20) << "r=" << r << dendl;
-
-  if (r < 0 || !m_image_ctx->exclusive_lock->is_lock_owner()) {
-    lderr(m_cct) << "cannot obtain exclusive lock - not removing" << dendl;
-    send_close_image(-EBUSY);
-    return;
-  }
-
-  validate_image_removal();
-}
-
-template<typename I>
-void RemoveRequest<I>::validate_image_removal() {
-  ldout(m_cct, 20) << dendl;
-
-  if (!m_image_ctx->ignore_migrating &&
-      m_image_ctx->test_features(RBD_FEATURE_MIGRATING)) {
-    lderr(m_cct) << "image in migration state - not removing" << dendl;
-    send_close_image(-EBUSY);
-    return;
-  }
+void RemoveRequest<I>::pre_remove_image() {
+  ldout(m_cct, 5) << dendl;
 
-  check_image_snaps();
-}
-
-template<typename I>
-void RemoveRequest<I>::check_image_snaps() {
-  ldout(m_cct, 20) << dendl;
-
-  m_image_ctx->snap_lock.get_read();
-  for (auto& snap_info : m_image_ctx->snap_info) {
-    if (auto_delete_snapshot(snap_info.second)) {
-      m_snap_infos.insert(snap_info);
-    } else {
-      m_image_ctx->snap_lock.put_read();
-
-      lderr(m_cct) << "image has snapshots - not removing" << dendl;
-      send_close_image(-ENOTEMPTY);
-      return;
-    }
-  }
-  m_image_ctx->snap_lock.put_read();
-
-  list_image_watchers();
-}
-
-template<typename I>
-void RemoveRequest<I>::list_image_watchers() {
-  ldout(m_cct, 20) << dendl;
-
-  int flags = LIST_WATCHERS_FILTER_OUT_MY_INSTANCE |
-              LIST_WATCHERS_FILTER_OUT_MIRROR_INSTANCES;
-  using klass = RemoveRequest<I>;
-  Context *ctx = create_context_callback<
-    klass, &klass::handle_list_image_watchers>(this);
-
-  auto req = ListWatchersRequest<I>::create(*m_image_ctx, flags, &m_watchers,
-                                            ctx);
-  req->send();
-}
-
-template<typename I>
-void RemoveRequest<I>::handle_list_image_watchers(int r) {
-  ldout(m_cct, 20) << "r=" << r << dendl;
-
-  if (r < 0) {
-    lderr(m_cct) << "error listing image watchers: " << cpp_strerror(r)
-                 << dendl;
-    send_close_image(r);
-    return;
-  }
-
-  check_image_watchers();
-}
-
-template<typename I>
-void RemoveRequest<I>::check_image_watchers() {
-  if (!m_watchers.empty()) {
-    lderr(m_cct) << "image has watchers - not removing" << dendl;
-    send_close_image(-EBUSY);
-    return;
-  }
-
-  check_group();
-}
-
-template<typename I>
-void RemoveRequest<I>::check_group() {
-  if (m_old_format) {
-    trim_image();
-    return;
-  }
-
-  ldout(m_cct, 20) << dendl;
-
-  librados::ObjectReadOperation op;
-  librbd::cls_client::image_group_get_start(&op);
-
-  using klass = RemoveRequest<I>;
-  librados::AioCompletion *rados_completion = create_rados_callback<
-    klass, &klass::handle_check_group>(this);
-  m_out_bl.clear();
-  int r = m_image_ctx->md_ctx.aio_operate(m_header_oid, rados_completion, &op,
-                                          &m_out_bl);
-  ceph_assert(r == 0);
-  rados_completion->release();
-}
-
-template<typename I>
-void RemoveRequest<I>::handle_check_group(int r) {
-  ldout(m_cct, 20) << "r=" << r << dendl;
-
-  cls::rbd::GroupSpec s;
-  if (r == 0) {
-    auto it = m_out_bl.cbegin();
-    r = librbd::cls_client::image_group_get_finish(&it, &s);
-  }
-  if (r < 0 && r != -EOPNOTSUPP) {
-    lderr(m_cct) << "error fetching group for image: "
-                 << cpp_strerror(r) << dendl;
-    send_close_image(r);
-    return;
-  }
-
-  if (s.is_valid()) {
-    lderr(m_cct) << "image is in a group - not removing" << dendl;
-    send_close_image(-EMLINK);
-    return;
-  }
-
-  remove_snapshot();
-}
-
-template<typename I>
-void RemoveRequest<I>::remove_snapshot() {
-  if (m_snap_infos.empty()) {
-    trim_image();
-    return;
-  }
-
-  auto snap_id = m_snap_infos.begin()->first;
-  auto& snap_info = m_snap_infos.begin()->second;
-  ldout(m_cct, 20) << "snap_id=" << snap_id << ", "
-                   << "snap_name=" << snap_info.name << dendl;
-
-  RWLock::RLocker owner_lock(m_image_ctx->owner_lock);
   auto ctx = create_context_callback<
-    RemoveRequest<I>, &RemoveRequest<I>::handle_remove_snapshot>(this);
-  auto req = librbd::operation::SnapshotRemoveRequest<I>::create(
-    *m_image_ctx, snap_info.snap_namespace, snap_info.name,
-    snap_id, ctx);
+    RemoveRequest<I>, &RemoveRequest<I>::handle_pre_remove_image>(this);
+  auto req = PreRemoveRequest<I>::create(m_image_ctx, m_force, ctx);
   req->send();
 }
 
 template<typename I>
-void RemoveRequest<I>::handle_remove_snapshot(int r) {
-  ldout(m_cct, 20) << "r=" << r << dendl;
-
-  if (r < 0 && r != -ENOENT) {
-    auto snap_id = m_snap_infos.begin()->first;
-    lderr(m_cct) << "failed to auto-prune snapshot " << snap_id << ": "
-                 << cpp_strerror(r) << dendl;
+void RemoveRequest<I>::handle_pre_remove_image(int r) {
+  ldout(m_cct, 5) << "r=" << r << dendl;
 
-    if (r == -EBUSY) {
-      r = -ENOTEMPTY;
-    }
+  if (r < 0) {
     send_close_image(r);
     return;
   }
 
-  ceph_assert(!m_snap_infos.empty());
-  m_snap_infos.erase(m_snap_infos.begin());
-
-  remove_snapshot();
+  trim_image();
 }
 
 template<typename I>
index 75288d9ae1423c27eb490e2456ee2faca3eccfd8..40a571e5aab85b9e895101638188a83127d01fc8 100644 (file)
@@ -60,53 +60,41 @@ private:
    *       (skip if already opened) OPEN IMAGE------------------\
    *                                     |                      |
    *                                     v                      |
-   *      error                   CHECK EXCLUSIVE LOCK---\     |
-   * /-------<-------\                   |                |     |
-   * |               |                   |            (acquired)|
-   * |               |                   v                |     |
-   * |               |           ACQUIRE EXCLUSIVE LOCK   |     |
-   * |               |               /   |                |     |
-   * |               |------<-------/    |                |     |
-   * |               |                   v                |     |
-   * |               |            VALIDATE IMAGE REMOVAL<-/     |
-   * |               |                /  |                      v
-   * |               \------<--------/   |   /------\          |
-   * |                                   v   v      |           |
-   * |                             REMOVE SNAPS ----/           |
-   * |                                   |                      |
-   * |                                   v                      |
-   * |                              TRIM IMAGE                  |
-   * |                                   |                      |
-   * v                                   v                      |
-   * |                              DETACH CHILD                |
-   * |                                   |                      |
-   * |                                   v                      v
-   * \--------->------------------>CLOSE IMAGE                  |
+   *                              PRE REMOVE IMAGE * * *        |
+   *                                     |             *        |
+   *                                     v             *        |
+   *                                TRIM IMAGE * * * * *        |
+   *                                     |             *        |
+   *                                     v             *        |
+   *                                DETACH CHILD       *        |
+   *                                     |             *        |
+   *                                     v             *        v
+   *                               CLOSE IMAGE < * * * *        |
    *                                     |                      |
-   *      error                         v                      |
+   *                               error v                      |
    * /------<--------\              REMOVE HEADER<--------------/
-   * |                      |                /  |
-   * |              |-------<-------/   |
+   * |               |                /  |
+   * |               |-------<-------/   |
    * |               |                   v
    * |               |              REMOVE JOURNAL
-   * |                      |                /  |
-   * |              |-------<-------/   |
+   * |               |                /  |
+   * |               |-------<-------/   |
    * |               |                   v
-   * v              ^          REMOVE OBJECTMAP
-   * |                      |                /  |
-   * |              |-------<-------/   |
+   * v               ^          REMOVE OBJECTMAP
+   * |               |                /  |
+   * |               |-------<-------/   |
    * |               |                   v
-   * |              |            REMOVE MIRROR IMAGE
-   * |                      |                /  |
-   * |              |-------<-------/   |
+   * |               |    REMOVE MIRROR IMAGE
+   * |               |                /  |
+   * |               |-------<-------/   |
    * |               |                   v
    * |               |            REMOVE ID OBJECT
-   * |                      |                /  |
-   * |              |-------<-------/   |
+   * |               |                /  |
+   * |               |-------<-------/   |
    * |               |                   v
    * |               |              REMOVE IMAGE
-   * |                      |                /  |
-   * |              \-------<-------/   |
+   * |               |                /  |
+   * |               \-------<-------/   |
    * |                                   v
    * \------------------>------------<finish>
    *
@@ -159,25 +147,8 @@ private:
   void mirror_image_remove();
   void handle_mirror_image_remove(int r);
 
-  void check_exclusive_lock();
-
-  void acquire_exclusive_lock();
-  void handle_exclusive_lock(int r);
-  void handle_exclusive_lock_force(int r);
-
-  void validate_image_removal();
-  void check_image_snaps();
-
-  void list_image_watchers();
-  void handle_list_image_watchers(int r);
-
-  void check_image_watchers();
-
-  void check_group();
-  void handle_check_group(int r);
-
-  void remove_snapshot();
-  void handle_remove_snapshot(int r);
+  void pre_remove_image();
+  void handle_pre_remove_image(int r);
 
   void trim_image();
   void handle_trim_image(int r);
index 3295afe8d7a183d11bc454fdc0bb6a79f117acec..945e22800a4b7b1ff541bdd487227811daefd1af 100644 (file)
@@ -65,6 +65,7 @@ set(unittest_librbd_srcs
   image/test_mock_DetachChildRequest.cc
   image/test_mock_DetachParentRequest.cc
   image/test_mock_ListWatchersRequest.cc
+  image/test_mock_PreRemoveRequest.cc
   image/test_mock_RefreshRequest.cc
   image/test_mock_RemoveRequest.cc
   io/test_mock_ImageRequest.cc
diff --git a/src/test/librbd/image/test_mock_PreRemoveRequest.cc b/src/test/librbd/image/test_mock_PreRemoveRequest.cc
new file mode 100644 (file)
index 0000000..131b1d8
--- /dev/null
@@ -0,0 +1,400 @@
+// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "librbd/image/ListWatchersRequest.h"
+#include "librbd/image/PreRemoveRequest.h"
+#include "librbd/image/RefreshParentRequest.h"
+#include "librbd/operation/SnapshotRemoveRequest.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <arpa/inet.h>
+#include <list>
+#include <boost/scope_exit.hpp>
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+  MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+  }
+};
+
+} // anonymous namespace
+
+namespace operation {
+
+template <>
+class SnapshotRemoveRequest<MockTestImageCtx> {
+public:
+  static SnapshotRemoveRequest *s_instance;
+  static SnapshotRemoveRequest *create(MockTestImageCtx &image_ctx,
+                                       cls::rbd::SnapshotNamespace sn,
+                                       std::string name,
+                                       uint64_t id, Context *on_finish) {
+    ceph_assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  Context *on_finish = nullptr;
+
+  SnapshotRemoveRequest() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+SnapshotRemoveRequest<MockTestImageCtx> *SnapshotRemoveRequest<MockTestImageCtx>::s_instance;
+
+} // namespace operation
+
+namespace image {
+
+template<>
+class ListWatchersRequest<MockTestImageCtx> {
+public:
+  static ListWatchersRequest *s_instance;
+  Context *on_finish = nullptr;
+
+  static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags,
+                                     std::list<obj_watch_t> *watchers,
+                                     Context *on_finish) {
+    ceph_assert(s_instance != nullptr);
+    s_instance->on_finish = on_finish;
+    return s_instance;
+  }
+
+  ListWatchersRequest() {
+    s_instance = this;
+  }
+
+  MOCK_METHOD0(send, void());
+};
+
+ListWatchersRequest<MockTestImageCtx> *ListWatchersRequest<MockTestImageCtx>::s_instance;
+
+} // namespace image
+} // namespace librbd
+
+// template definitions
+#include "librbd/image/PreRemoveRequest.cc"
+
+ACTION_P(TestFeatures, image_ctx) {
+  return ((image_ctx->features & arg0) != 0);
+}
+
+ACTION_P(ShutDownExclusiveLock, image_ctx) {
+  // shutting down exclusive lock will close object map and journal
+  image_ctx->exclusive_lock = nullptr;
+  image_ctx->object_map = nullptr;
+  image_ctx->journal = nullptr;
+}
+
+namespace librbd {
+namespace image {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+
+class TestMockImagePreRemoveRequest : public TestMockFixture {
+public:
+  typedef PreRemoveRequest<MockTestImageCtx> MockPreRemoveRequest;
+  typedef ListWatchersRequest<MockTestImageCtx> MockListWatchersRequest;
+  typedef librbd::operation::SnapshotRemoveRequest<MockTestImageCtx> MockSnapshotRemoveRequest;
+
+  void SetUp() override {
+    TestMockFixture::SetUp();
+
+    ASSERT_EQ(0, open_image(m_image_name, &m_test_imctx));
+    m_mock_imctx = new MockTestImageCtx(*m_test_imctx);
+  }
+
+  void TearDown() override {
+    delete m_mock_imctx;
+    TestMockFixture::TearDown();
+  }
+
+  void expect_test_features(MockTestImageCtx &mock_image_ctx) {
+    EXPECT_CALL(mock_image_ctx, test_features(_))
+      .WillRepeatedly(TestFeatures(&mock_image_ctx));
+  }
+
+  void expect_set_journal_policy(MockTestImageCtx &mock_image_ctx) {
+    if (m_test_imctx->test_features(RBD_FEATURE_JOURNALING)) {
+      EXPECT_CALL(mock_image_ctx, set_journal_policy(_))
+        .WillOnce(Invoke([](journal::Policy* policy) {
+                    ASSERT_TRUE(policy->journal_disabled());
+                    delete policy;
+                  }));
+    }
+  }
+
+  void expect_try_acquire_exclusive_lock(MockTestImageCtx &mock_image_ctx,
+                                       MockExclusiveLock &mock_exclusive_lock,
+                                       int r) {
+    if (m_mock_imctx->exclusive_lock != nullptr) {
+      EXPECT_CALL(mock_exclusive_lock, try_acquire_lock(_))
+        .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+    }
+  }
+
+  void expect_shut_down_exclusive_lock(MockTestImageCtx &mock_image_ctx,
+                                       MockExclusiveLock &mock_exclusive_lock,
+                                       int r) {
+    if (m_mock_imctx->exclusive_lock != nullptr) {
+      EXPECT_CALL(mock_exclusive_lock, shut_down(_))
+        .WillOnce(DoAll(ShutDownExclusiveLock(&mock_image_ctx),
+                        CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)));
+    }
+  }
+
+  void expect_is_exclusive_lock_owner(MockTestImageCtx &mock_image_ctx,
+                                      MockExclusiveLock &mock_exclusive_lock,
+                                      bool is_owner) {
+    if (m_mock_imctx->exclusive_lock != nullptr) {
+      EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillOnce(Return(is_owner));
+    }
+  }
+
+  void expect_list_image_watchers(
+    MockTestImageCtx &mock_image_ctx,
+    MockListWatchersRequest &mock_list_watchers_request, int r) {
+    EXPECT_CALL(mock_list_watchers_request, send())
+      .WillOnce(FinishRequest(&mock_list_watchers_request, r, &mock_image_ctx));
+  }
+
+  void expect_get_group(MockTestImageCtx &mock_image_ctx, int r) {
+    if (mock_image_ctx.old_format) {
+      return;
+    }
+
+    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+                               exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+                                    StrEq("image_group_get"), _, _, _));
+    if (r < 0) {
+      expect.WillOnce(Return(r));
+    } else {
+      expect.WillOnce(DoDefault());
+    }
+  }
+
+  void expect_remove_snap(MockTestImageCtx &mock_image_ctx,
+                          MockSnapshotRemoveRequest& mock_snap_remove_request,
+                          int r) {
+    EXPECT_CALL(mock_snap_remove_request, send())
+      .WillOnce(FinishRequest(&mock_snap_remove_request, r, &mock_image_ctx));
+  }
+
+  librbd::ImageCtx *m_test_imctx = nullptr;
+  MockTestImageCtx *m_mock_imctx = nullptr;
+};
+
+TEST_F(TestMockImagePreRemoveRequest, Success) {
+  MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock();
+  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    m_mock_imctx->exclusive_lock = mock_exclusive_lock;
+  }
+
+  expect_op_work_queue(*m_mock_imctx);
+  expect_test_features(*m_mock_imctx);
+
+  InSequence seq;
+  expect_set_journal_policy(*m_mock_imctx);
+  expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, 0);
+
+  MockListWatchersRequest mock_list_watchers_request;
+  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
+
+  expect_get_group(*m_mock_imctx, 0);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, true, &ctx);
+  req->send();
+
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, OperationsDisabled) {
+  REQUIRE_FORMAT_V2();
+
+  m_mock_imctx->operations_disabled = true;
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx);
+  req->send();
+
+  ASSERT_EQ(-EROFS, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockShutDownFailed) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock();
+  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    m_mock_imctx->exclusive_lock = mock_exclusive_lock;
+  }
+
+  expect_op_work_queue(*m_mock_imctx);
+  expect_test_features(*m_mock_imctx);
+
+  InSequence seq;
+  expect_set_journal_policy(*m_mock_imctx);
+  expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, true, &ctx);
+  req->send();
+
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, ExclusiveLockTryAcquireFailed) {
+  REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+  MockExclusiveLock mock_exclusive_lock;
+  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    m_mock_imctx->exclusive_lock = &mock_exclusive_lock;
+  }
+
+  expect_op_work_queue(*m_mock_imctx);
+  expect_test_features(*m_mock_imctx);
+
+  InSequence seq;
+  expect_set_journal_policy(*m_mock_imctx);
+  expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock,
+                                    -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx);
+  req->send();
+
+  ASSERT_EQ(-EBUSY, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, Migration) {
+  m_mock_imctx->features |= RBD_FEATURE_MIGRATING;
+
+  expect_test_features(*m_mock_imctx);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx);
+  req->send();
+
+  ASSERT_EQ(-EBUSY, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, Snapshots) {
+  m_mock_imctx->snap_info = {
+    {123, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}};
+
+  expect_test_features(*m_mock_imctx);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx);
+  req->send();
+
+  ASSERT_EQ(-ENOTEMPTY, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, Watchers) {
+  MockExclusiveLock mock_exclusive_lock;
+  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    m_mock_imctx->exclusive_lock = &mock_exclusive_lock;
+  }
+
+  expect_op_work_queue(*m_mock_imctx);
+  expect_test_features(*m_mock_imctx);
+
+  InSequence seq;
+  expect_set_journal_policy(*m_mock_imctx);
+  expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0);
+  expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true);
+
+  MockListWatchersRequest mock_list_watchers_request;
+  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request,
+                             -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx);
+  req->send();
+
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, GroupError) {
+  REQUIRE_FORMAT_V2();
+
+  MockExclusiveLock mock_exclusive_lock;
+  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    m_mock_imctx->exclusive_lock = &mock_exclusive_lock;
+  }
+
+  expect_op_work_queue(*m_mock_imctx);
+  expect_test_features(*m_mock_imctx);
+
+  InSequence seq;
+  expect_set_journal_policy(*m_mock_imctx);
+  expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0);
+  expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true);
+
+  MockListWatchersRequest mock_list_watchers_request;
+  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
+
+  expect_get_group(*m_mock_imctx, -EINVAL);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx);
+  req->send();
+
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImagePreRemoveRequest, AutoDeleteSnapshots) {
+  REQUIRE_FORMAT_V2();
+
+  MockExclusiveLock mock_exclusive_lock;
+  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+    m_mock_imctx->exclusive_lock = &mock_exclusive_lock;
+  }
+
+  expect_op_work_queue(*m_mock_imctx);
+  expect_test_features(*m_mock_imctx);
+
+  m_mock_imctx->snap_info = {
+    {123, {"snap1", {cls::rbd::TrashSnapshotNamespace{}}, {}, {}, {}, {}, {}}}};
+
+  InSequence seq;
+  expect_set_journal_policy(*m_mock_imctx);
+  expect_try_acquire_exclusive_lock(*m_mock_imctx, mock_exclusive_lock, 0);
+  expect_is_exclusive_lock_owner(*m_mock_imctx, mock_exclusive_lock, true);
+
+  MockListWatchersRequest mock_list_watchers_request;
+  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
+
+  expect_get_group(*m_mock_imctx, 0);
+
+  MockSnapshotRemoveRequest mock_snap_remove_request;
+  expect_remove_snap(*m_mock_imctx, mock_snap_remove_request, 0);
+
+  C_SaferCond ctx;
+  auto req = MockPreRemoveRequest::create(m_mock_imctx, false, &ctx);
+  req->send();
+
+  ASSERT_EQ(0, ctx.wait());
+}
+
+} // namespace image
+} // namespace librbd
index c9ea3796530099164d744a82f4245e305a5c1c9e..05f4a546fe3ea23c0cf3d922ba5704d91486e0e9 100644 (file)
@@ -9,15 +9,12 @@
 #include "test/librados_test_stub/MockTestMemRadosClient.h"
 #include "librbd/ImageState.h"
 #include "librbd/internal.h"
-#include "librbd/Operations.h"
 #include "librbd/image/TypeTraits.h"
 #include "librbd/image/DetachChildRequest.h"
-#include "librbd/image/ListWatchersRequest.h"
+#include "librbd/image/PreRemoveRequest.h"
 #include "librbd/image/RemoveRequest.h"
-#include "librbd/image/RefreshParentRequest.h"
 #include "librbd/journal/RemoveRequest.h"
 #include "librbd/mirror/DisableRequest.h"
-#include "librbd/operation/SnapshotRemoveRequest.h"
 #include "librbd/operation/TrimRequest.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -46,6 +43,7 @@ struct MockTestImageCtx : public MockImageCtx {
 MockTestImageCtx* MockTestImageCtx::s_instance = nullptr;
 
 } // anonymous namespace
+
 namespace image {
 
 template <>
@@ -75,18 +73,12 @@ public:
 
 DetachChildRequest<MockTestImageCtx> *DetachChildRequest<MockTestImageCtx>::s_instance;
 
-} // namespace image
-
-namespace operation {
-
 template <>
-class SnapshotRemoveRequest<MockTestImageCtx> {
+class PreRemoveRequest<MockTestImageCtx> {
 public:
-  static SnapshotRemoveRequest *s_instance;
-  static SnapshotRemoveRequest *create(MockTestImageCtx &image_ctx,
-                                       cls::rbd::SnapshotNamespace sn,
-                                       std::string name,
-                                       uint64_t id, Context *on_finish) {
+  static PreRemoveRequest *s_instance;
+  static PreRemoveRequest *create(MockTestImageCtx* image_ctx, bool force,
+                                  Context* on_finish) {
     ceph_assert(s_instance != nullptr);
     s_instance->on_finish = on_finish;
     return s_instance;
@@ -94,14 +86,18 @@ public:
 
   Context *on_finish = nullptr;
 
-  SnapshotRemoveRequest() {
+  PreRemoveRequest() {
     s_instance = this;
   }
 
   MOCK_METHOD0(send, void());
 };
 
-SnapshotRemoveRequest<MockTestImageCtx> *SnapshotRemoveRequest<MockTestImageCtx>::s_instance;
+PreRemoveRequest<MockTestImageCtx> *PreRemoveRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image
+
+namespace operation {
 
 template <>
 class TrimRequest<MockTestImageCtx> {
@@ -183,49 +179,11 @@ public:
 DisableRequest<MockTestImageCtx> *DisableRequest<MockTestImageCtx>::s_instance;
 
 } // namespace mirror
-
-namespace image {
-
-template<>
-class ListWatchersRequest<MockTestImageCtx> {
-public:
-  static ListWatchersRequest *s_instance;
-  Context *on_finish = nullptr;
-
-  static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags,
-                                     std::list<obj_watch_t> *watchers,
-                                     Context *on_finish) {
-    ceph_assert(s_instance != nullptr);
-    s_instance->on_finish = on_finish;
-    return s_instance;
-  }
-
-  ListWatchersRequest() {
-    s_instance = this;
-  }
-
-  MOCK_METHOD0(send, void());
-};
-
-ListWatchersRequest<MockTestImageCtx> *ListWatchersRequest<MockTestImageCtx>::s_instance;
-
-} // namespace image
 } // namespace librbd
 
 // template definitions
 #include "librbd/image/RemoveRequest.cc"
 
-ACTION_P(TestFeatures, image_ctx) {
-  return ((image_ctx->features & arg0) != 0);
-}
-
-ACTION_P(ShutDownExclusiveLock, image_ctx) {
-  // shutting down exclusive lock will close object map and journal
-  image_ctx->exclusive_lock = nullptr;
-  image_ctx->object_map = nullptr;
-  image_ctx->journal = nullptr;
-}
-
 namespace librbd {
 namespace image {
 
@@ -244,9 +202,8 @@ public:
   typedef ::librbd::image::TypeTraits<MockTestImageCtx> TypeTraits;
   typedef typename TypeTraits::ContextWQ ContextWQ;
   typedef RemoveRequest<MockTestImageCtx> MockRemoveRequest;
+  typedef PreRemoveRequest<MockTestImageCtx> MockPreRemoveRequest;
   typedef DetachChildRequest<MockTestImageCtx> MockDetachChildRequest;
-  typedef ListWatchersRequest<MockTestImageCtx> MockListWatchersRequest;
-  typedef librbd::operation::SnapshotRemoveRequest<MockTestImageCtx> MockSnapshotRemoveRequest;
   typedef librbd::operation::TrimRequest<MockTestImageCtx> MockTrimRequest;
   typedef librbd::journal::RemoveRequest<MockTestImageCtx> MockJournalRemoveRequest;
   typedef librbd::mirror::DisableRequest<MockTestImageCtx> MockMirrorDisableRequest;
@@ -254,7 +211,6 @@ public:
   librbd::ImageCtx *m_test_imctx = NULL;
   MockTestImageCtx *m_mock_imctx = NULL;
 
-
   void SetUp() override {
     TestMockFixture::SetUp();
 
@@ -293,29 +249,10 @@ public:
                 }));
   }
 
-  void expect_list_image_watchers(
-    MockTestImageCtx &mock_image_ctx,
-    MockListWatchersRequest &mock_list_watchers_request, int r) {
-    EXPECT_CALL(mock_list_watchers_request, send())
-      .WillOnce(FinishRequest(&mock_list_watchers_request, r, &mock_image_ctx));
-  }
-
-  void expect_get_group(MockTestImageCtx &mock_image_ctx, int r) {
-    auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
-                               exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
-                                    StrEq("image_group_get"), _, _, _));
-    if (r < 0) {
-      expect.WillOnce(Return(r));
-    } else {
-      expect.WillOnce(DoDefault());
-    }
-  }
-
-  void expect_remove_snap(MockTestImageCtx &mock_image_ctx,
-                          MockSnapshotRemoveRequest& mock_snap_remove_request,
-                          int r) {
-    EXPECT_CALL(mock_snap_remove_request, send())
-      .WillOnce(FinishRequest(&mock_snap_remove_request, r, &mock_image_ctx));
+  void expect_pre_remove_image(MockTestImageCtx &mock_image_ctx,
+                               MockPreRemoveRequest& mock_request, int r) {
+    EXPECT_CALL(mock_request, send())
+      .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx));
   }
 
   void expect_trim(MockTestImageCtx &mock_image_ctx,
@@ -355,32 +292,6 @@ public:
     EXPECT_CALL(mock_request, send())
       .WillOnce(FinishRequest(&mock_request, r, &mock_image_ctx));
   }
-
-  void expect_test_features(MockTestImageCtx &mock_image_ctx) {
-    EXPECT_CALL(mock_image_ctx, test_features(_))
-      .WillRepeatedly(TestFeatures(&mock_image_ctx));
-  }
-
-  void expect_set_journal_policy(MockTestImageCtx &mock_image_ctx) {
-    if (m_test_imctx->test_features(RBD_FEATURE_JOURNALING)) {
-      EXPECT_CALL(mock_image_ctx, set_journal_policy(_))
-        .WillOnce(Invoke([](journal::Policy* policy) {
-                    ASSERT_TRUE(policy->journal_disabled());
-                    delete policy;
-                  }));
-    }
-  }
-
-  void expect_shut_down_exclusive_lock(MockTestImageCtx &mock_image_ctx,
-                                       MockExclusiveLock &mock_exclusive_lock,
-                                       int r) {
-    if (m_mock_imctx->exclusive_lock != nullptr) {
-      EXPECT_CALL(mock_exclusive_lock, shut_down(_))
-        .WillOnce(DoAll(ShutDownExclusiveLock(&mock_image_ctx),
-                        CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue)));
-    }
-  }
-
 };
 
 TEST_F(TestMockImageRemoveRequest, SuccessV1) {
@@ -389,10 +300,9 @@ TEST_F(TestMockImageRemoveRequest, SuccessV1) {
 
   InSequence seq;
   expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
 
-  MockListWatchersRequest mock_list_watchers_request;
-  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
+  MockPreRemoveRequest mock_pre_remove_request;
+  expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0);
 
   MockTrimRequest mock_trim_request;
   expect_trim(*m_mock_imctx, mock_trim_request, 0);
@@ -432,11 +342,6 @@ TEST_F(TestMockImageRemoveRequest, OpenFailV1) {
 TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV1) {
   REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
 
-  MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock();
-  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
-    m_mock_imctx->exclusive_lock = mock_exclusive_lock;
-  }
-
   expect_op_work_queue(*m_mock_imctx);
 
   m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id();
@@ -445,20 +350,9 @@ TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV1) {
 
   InSequence seq;
   expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
-
-  if (m_mock_imctx->exclusive_lock != nullptr) {
-    expect_test_features(*m_mock_imctx);
-  }
-  expect_set_journal_policy(*m_mock_imctx);
-  expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, 0);
-
-  expect_test_features(*m_mock_imctx);
-
-  MockListWatchersRequest mock_list_watchers_request;
-  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
 
-  expect_get_group(*m_mock_imctx, 0);
+  MockPreRemoveRequest mock_pre_remove_request;
+  expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0);
 
   MockTrimRequest mock_trim_request;
   expect_trim(*m_mock_imctx, mock_trim_request, 0);
@@ -490,11 +384,6 @@ TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV1) {
 TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV2) {
   REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
 
-  MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock();
-  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
-    m_mock_imctx->exclusive_lock = mock_exclusive_lock;
-  }
-
   expect_op_work_queue(*m_mock_imctx);
 
   m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id();
@@ -503,20 +392,9 @@ TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV2) {
 
   InSequence seq;
   expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
 
-  if (m_mock_imctx->exclusive_lock != nullptr) {
-    expect_test_features(*m_mock_imctx);
-  }
-  expect_set_journal_policy(*m_mock_imctx);
-  expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, 0);
-
-  expect_test_features(*m_mock_imctx);
-
-  MockListWatchersRequest mock_list_watchers_request;
-  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
-
-  expect_get_group(*m_mock_imctx, 0);
+  MockPreRemoveRequest mock_pre_remove_request;
+  expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0);
 
   MockTrimRequest mock_trim_request;
   expect_trim(*m_mock_imctx, mock_trim_request, 0);
@@ -548,11 +426,6 @@ TEST_F(TestMockImageRemoveRequest, SuccessV2CloneV2) {
 TEST_F(TestMockImageRemoveRequest, NotExistsV2) {
   REQUIRE_FEATURE(RBD_FEATURE_JOURNALING);
 
-  MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock();
-  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
-    m_mock_imctx->exclusive_lock = mock_exclusive_lock;
-  }
-
   expect_op_work_queue(*m_mock_imctx);
 
   m_mock_imctx->parent_md.spec.pool_id = m_ioctx.get_id();
@@ -561,18 +434,9 @@ TEST_F(TestMockImageRemoveRequest, NotExistsV2) {
 
   InSequence seq;
   expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
-
-  expect_test_features(*m_mock_imctx);
-  expect_set_journal_policy(*m_mock_imctx);
-  expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, 0);
-
-  expect_test_features(*m_mock_imctx);
 
-  MockListWatchersRequest mock_list_watchers_request;
-  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
-
-  expect_get_group(*m_mock_imctx, 0);
+  MockPreRemoveRequest mock_pre_remove_request;
+  expect_pre_remove_image(*m_mock_imctx, mock_pre_remove_request, 0);
 
   MockTrimRequest mock_trim_request;
   expect_trim(*m_mock_imctx, mock_trim_request, 0);
@@ -600,126 +464,5 @@ TEST_F(TestMockImageRemoveRequest, NotExistsV2) {
   ASSERT_EQ(-ENOENT, ctx.wait());
 }
 
-TEST_F(TestMockImageRemoveRequest, OperationsDisabled) {
-  MockExclusiveLock mock_exclusive_lock;
-  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
-    m_mock_imctx->exclusive_lock = &mock_exclusive_lock;
-  }
-
-  m_mock_imctx->operations_disabled = true;
-
-  InSequence seq;
-  expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
-  expect_state_close(*m_mock_imctx);
-
-  C_SaferCond ctx;
-  librbd::NoOpProgressContext no_op;
-  ContextWQ op_work_queue;
-  MockRemoveRequest *req = MockRemoveRequest::create(
-    m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx);
-  req->send();
-
-  ASSERT_EQ(-EROFS, ctx.wait());
-}
-
-TEST_F(TestMockImageRemoveRequest, Migration) {
-  m_mock_imctx->features |= RBD_FEATURE_MIGRATING;
-
-  InSequence seq;
-  expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
-  expect_state_close(*m_mock_imctx);
-
-  C_SaferCond ctx;
-  librbd::NoOpProgressContext no_op;
-  ContextWQ op_work_queue;
-  MockRemoveRequest *req = MockRemoveRequest::create(
-    m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx);
-  req->send();
-
-  ASSERT_EQ(-EBUSY, ctx.wait());
-}
-
-TEST_F(TestMockImageRemoveRequest, Snapshots) {
-  m_mock_imctx->snap_info = {
-    {123, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}};
-
-  InSequence seq;
-  expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
-  expect_state_close(*m_mock_imctx);
-
-  C_SaferCond ctx;
-  librbd::NoOpProgressContext no_op;
-  ContextWQ op_work_queue;
-  MockRemoveRequest *req = MockRemoveRequest::create(
-    m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx);
-  req->send();
-
-  ASSERT_EQ(-ENOTEMPTY, ctx.wait());
-}
-
-TEST_F(TestMockImageRemoveRequest, AutoDeleteSnapshots) {
-  REQUIRE_FORMAT_V2();
-
-  MockExclusiveLock *mock_exclusive_lock = new MockExclusiveLock();
-  if (m_test_imctx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
-    m_mock_imctx->exclusive_lock = mock_exclusive_lock;
-  }
-
-  expect_op_work_queue(*m_mock_imctx);
-
-  m_mock_imctx->snap_info = {
-    {123, {"snap1", {cls::rbd::TrashSnapshotNamespace{}}, {}, {}, {}, {}, {}}}};
-
-  InSequence seq;
-  expect_state_open(*m_mock_imctx, 0);
-  expect_test_features(*m_mock_imctx);
-
-  if (m_mock_imctx->exclusive_lock != nullptr) {
-    expect_test_features(*m_mock_imctx);
-  }
-  expect_set_journal_policy(*m_mock_imctx);
-  expect_shut_down_exclusive_lock(*m_mock_imctx, *mock_exclusive_lock, 0);
-
-  expect_test_features(*m_mock_imctx);
-
-  MockListWatchersRequest mock_list_watchers_request;
-  expect_list_image_watchers(*m_mock_imctx, mock_list_watchers_request, 0);
-
-  expect_get_group(*m_mock_imctx, 0);
-
-  MockSnapshotRemoveRequest mock_snap_remove_request;
-  expect_remove_snap(*m_mock_imctx, mock_snap_remove_request, 0);
-
-  MockTrimRequest mock_trim_request;
-  expect_trim(*m_mock_imctx, mock_trim_request, 0);
-
-  MockDetachChildRequest mock_detach_child_request;
-  expect_detach_child(*m_mock_imctx, mock_detach_child_request, 0);
-
-  MockMirrorDisableRequest mock_mirror_disable_request;
-  expect_mirror_disable(*m_mock_imctx, mock_mirror_disable_request, 0);
-
-  expect_state_close(*m_mock_imctx);
-
-  MockJournalRemoveRequest mock_journal_remove_request;
-  expect_journal_remove(*m_mock_imctx, mock_journal_remove_request, 0);
-
-  expect_remove_mirror_image(m_ioctx, 0);
-  expect_dir_remove_image(m_ioctx, 0);
-
-  C_SaferCond ctx;
-  librbd::NoOpProgressContext no_op;
-  ContextWQ op_work_queue;
-  MockRemoveRequest *req = MockRemoveRequest::create(
-    m_ioctx, m_image_name, "", true, false, no_op, &op_work_queue, &ctx);
-  req->send();
-
-  ASSERT_EQ(0, ctx.wait());
-
-}
-
 } // namespace image
 } // namespace librbd