]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd-mirror: move snap purge to standalone state machine
authorJason Dillaman <dillaman@redhat.com>
Thu, 16 Nov 2017 20:34:04 +0000 (15:34 -0500)
committerJason Dillaman <dillaman@redhat.com>
Tue, 21 Nov 2017 14:17:40 +0000 (09:17 -0500)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/test/rbd_mirror/CMakeLists.txt
src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc [new file with mode: 0644]
src/tools/rbd_mirror/CMakeLists.txt
src/tools/rbd_mirror/ImageDeleter.cc
src/tools/rbd_mirror/image_deleter/RemoveRequest.cc [new file with mode: 0644]
src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc [new file with mode: 0644]
src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h [new file with mode: 0644]

index 6d4e4cfa50143a36750cec85e4af78b5733f3501..48f2e249f942fa24478080648d49eb8265a73128 100644 (file)
@@ -24,6 +24,7 @@ add_executable(unittest_rbd_mirror
   test_mock_InstanceWatcher.cc
   test_mock_LeaderWatcher.cc
   test_mock_PoolWatcher.cc
+  image_deleter/test_mock_SnapshotPurgeRequest.cc
   image_replayer/test_mock_BootstrapRequest.cc
   image_replayer/test_mock_CreateImageRequest.cc
   image_replayer/test_mock_EventPreprocessor.cc
diff --git a/src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc b/src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc
new file mode 100644 (file)
index 0000000..bf31213
--- /dev/null
@@ -0,0 +1,395 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/rbd_mirror/test_mock_fixture.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockExclusiveLock.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockImageState.h"
+#include "test/librbd/mock/MockOperations.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+  static MockTestImageCtx *s_instance;
+  static MockTestImageCtx *create(const std::string &image_name,
+                                  const std::string &image_id,
+                                  const char *snap, librados::IoCtx& p,
+                                  bool read_only) {
+    assert(s_instance != nullptr);
+    return s_instance;
+  }
+
+  MockTestImageCtx(librbd::ImageCtx &image_ctx)
+      : librbd::MockImageCtx(image_ctx) {
+    s_instance = this;
+  }
+};
+
+MockTestImageCtx *MockTestImageCtx::s_instance = nullptr;
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_deleter {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::WithArg;
+
+class TestMockImageDeleterSnapshotPurgeRequest : public TestMockFixture {
+public:
+  typedef SnapshotPurgeRequest<librbd::MockTestImageCtx> MockSnapshotPurgeRequest;
+
+  void SetUp() override {
+    TestMockFixture::SetUp();
+
+    librbd::RBD rbd;
+    ASSERT_EQ(0, create_image(rbd, m_local_io_ctx, m_image_name, m_image_size));
+    ASSERT_EQ(0, open_image(m_local_io_ctx, m_image_name, &m_local_image_ctx));
+  }
+
+  void expect_set_journal_policy(librbd::MockTestImageCtx &mock_image_ctx) {
+    EXPECT_CALL(mock_image_ctx, set_journal_policy(_))
+      .WillOnce(Invoke([](librbd::journal::Policy* policy) {
+                  delete policy;
+                }));
+  }
+
+  void expect_open(librbd::MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(*mock_image_ctx.state, open(true, _))
+      .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) {
+                             m_threads->work_queue->queue(ctx, r);
+                           })));
+  }
+
+  void expect_close(librbd::MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(*mock_image_ctx.state, close(_))
+      .WillOnce(Invoke([this, r](Context* ctx) {
+                  m_threads->work_queue->queue(ctx, r);
+                }));
+  }
+
+  void expect_acquire_lock(librbd::MockTestImageCtx &mock_image_ctx, int r) {
+    EXPECT_CALL(*mock_image_ctx.exclusive_lock, acquire_lock(_))
+      .WillOnce(Invoke([this, r](Context* ctx) {
+                  m_threads->work_queue->queue(ctx, r);
+                }));
+  }
+
+  void expect_get_snap_namespace(librbd::MockTestImageCtx &mock_image_ctx,
+                                 uint64_t snap_id,
+                                 const cls::rbd::SnapshotNamespace &snap_namespace,
+                                 int r) {
+    EXPECT_CALL(mock_image_ctx, get_snap_namespace(snap_id, _))
+      .WillOnce(WithArg<1>(Invoke([snap_namespace, r](cls::rbd::SnapshotNamespace *ns) {
+                             *ns = snap_namespace;
+                             return r;
+                           })));
+  }
+
+  void expect_get_snap_name(librbd::MockTestImageCtx &mock_image_ctx,
+                            uint64_t snap_id, const std::string& name,
+                            int r) {
+    EXPECT_CALL(mock_image_ctx, get_snap_name(snap_id, _))
+      .WillOnce(WithArg<1>(Invoke([name, r](std::string *n) {
+                             *n = name;
+                             return r;
+                           })));
+  }
+
+  void expect_is_snap_protected(librbd::MockTestImageCtx &mock_image_ctx,
+                                uint64_t snap_id, bool is_protected, int r) {
+    EXPECT_CALL(mock_image_ctx, is_snap_protected(snap_id, _))
+      .WillOnce(WithArg<1>(Invoke([is_protected, r](bool *prot) {
+                             *prot = is_protected;
+                             return r;
+                           })));
+  }
+
+  void expect_snap_unprotect(librbd::MockTestImageCtx &mock_image_ctx,
+                             const cls::rbd::SnapshotNamespace& ns,
+                             const std::string& name, int r) {
+    EXPECT_CALL(*mock_image_ctx.operations, execute_snap_unprotect(ns, name, _))
+      .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) {
+                             m_threads->work_queue->queue(ctx, r);
+                           })));
+  }
+
+  void expect_snap_remove(librbd::MockTestImageCtx &mock_image_ctx,
+                          const cls::rbd::SnapshotNamespace& ns,
+                          const std::string& name, int r) {
+    EXPECT_CALL(*mock_image_ctx.operations, execute_snap_remove(ns, name, _))
+      .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) {
+                             m_threads->work_queue->queue(ctx, r);
+                           })));
+  }
+
+  void expect_start_op(librbd::MockTestImageCtx &mock_image_ctx, bool success) {
+    EXPECT_CALL(*mock_image_ctx.exclusive_lock, start_op())
+      .WillOnce(Invoke([success]() {
+                  if (!success) {
+                    return static_cast<FunctionContext*>(nullptr);
+                  }
+                  return new FunctionContext([](int r) {});
+                }));
+  }
+
+  void expect_destroy(librbd::MockTestImageCtx& mock_image_ctx) {
+    EXPECT_CALL(mock_image_ctx, destroy());
+  }
+
+  librbd::ImageCtx *m_local_image_ctx;
+};
+
+TEST_F(TestMockImageDeleterSnapshotPurgeRequest, Success) {
+  {
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1,
+                                0, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {});
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap2", 2,
+                                0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0,
+                                {});
+  }
+
+  librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx);
+  librbd::MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  InSequence seq;
+  expect_set_journal_policy(mock_image_ctx);
+  expect_open(mock_image_ctx, 0);
+  expect_acquire_lock(mock_image_ctx, 0);
+
+  expect_get_snap_namespace(mock_image_ctx, 2,
+                            cls::rbd::UserSnapshotNamespace{}, 0);
+  expect_get_snap_name(mock_image_ctx, 2, "snap2", 0);
+  expect_is_snap_protected(mock_image_ctx, 2, false, 0);
+  expect_start_op(mock_image_ctx, true);
+  expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap2",
+                     0);
+
+  expect_get_snap_namespace(mock_image_ctx, 1,
+                            cls::rbd::UserSnapshotNamespace{}, 0);
+  expect_get_snap_name(mock_image_ctx, 1, "snap1", 0);
+  expect_is_snap_protected(mock_image_ctx, 1, true, 0);
+  expect_start_op(mock_image_ctx, true);
+  expect_snap_unprotect(mock_image_ctx, cls::rbd::UserSnapshotNamespace{},
+                        "snap1", 0);
+  expect_start_op(mock_image_ctx, true);
+  expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap1",
+                     0);
+
+  expect_close(mock_image_ctx, 0);
+  expect_destroy(mock_image_ctx);
+
+  C_SaferCond ctx;
+  auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id,
+                                              &ctx);
+  req->send();
+  ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageDeleterSnapshotPurgeRequest, OpenError) {
+  {
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1,
+                                0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0,
+                                {});
+  }
+
+  librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx);
+  librbd::MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  InSequence seq;
+  expect_set_journal_policy(mock_image_ctx);
+  expect_open(mock_image_ctx, -EPERM);
+
+  C_SaferCond ctx;
+  auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id,
+                                              &ctx);
+  req->send();
+  ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockImageDeleterSnapshotPurgeRequest, AcquireLockError) {
+  {
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1,
+                                0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0,
+                                {});
+  }
+
+  librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx);
+  librbd::MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  InSequence seq;
+  expect_set_journal_policy(mock_image_ctx);
+  expect_open(mock_image_ctx, 0);
+  expect_acquire_lock(mock_image_ctx, -EPERM);
+  expect_close(mock_image_ctx, -EINVAL);
+  expect_destroy(mock_image_ctx);
+
+  C_SaferCond ctx;
+  auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id,
+                                              &ctx);
+  req->send();
+  ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockImageDeleterSnapshotPurgeRequest, SnapUnprotectBusy) {
+  {
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1,
+                                0, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {});
+  }
+
+  librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx);
+  librbd::MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  InSequence seq;
+  expect_set_journal_policy(mock_image_ctx);
+  expect_open(mock_image_ctx, 0);
+  expect_acquire_lock(mock_image_ctx, 0);
+
+  expect_get_snap_namespace(mock_image_ctx, 1,
+                            cls::rbd::UserSnapshotNamespace{}, 0);
+  expect_get_snap_name(mock_image_ctx, 1, "snap1", 0);
+  expect_is_snap_protected(mock_image_ctx, 1, true, 0);
+  expect_start_op(mock_image_ctx, true);
+  expect_snap_unprotect(mock_image_ctx, cls::rbd::UserSnapshotNamespace{},
+                        "snap1", -EBUSY);
+
+  expect_close(mock_image_ctx, -EINVAL);
+  expect_destroy(mock_image_ctx);
+
+  C_SaferCond ctx;
+  auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id,
+                                              &ctx);
+  req->send();
+  ASSERT_EQ(-EBUSY, ctx.wait());
+}
+
+TEST_F(TestMockImageDeleterSnapshotPurgeRequest, SnapUnprotectError) {
+  {
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1,
+                                0, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {});
+  }
+
+  librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx);
+  librbd::MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  InSequence seq;
+  expect_set_journal_policy(mock_image_ctx);
+  expect_open(mock_image_ctx, 0);
+  expect_acquire_lock(mock_image_ctx, 0);
+
+  expect_get_snap_namespace(mock_image_ctx, 1,
+                            cls::rbd::UserSnapshotNamespace{}, 0);
+  expect_get_snap_name(mock_image_ctx, 1, "snap1", 0);
+  expect_is_snap_protected(mock_image_ctx, 1, true, 0);
+  expect_start_op(mock_image_ctx, true);
+  expect_snap_unprotect(mock_image_ctx, cls::rbd::UserSnapshotNamespace{},
+                        "snap1", -EPERM);
+
+  expect_close(mock_image_ctx, -EINVAL);
+  expect_destroy(mock_image_ctx);
+
+  C_SaferCond ctx;
+  auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id,
+                                              &ctx);
+  req->send();
+  ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockImageDeleterSnapshotPurgeRequest, SnapRemoveError) {
+  {
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1,
+                                0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0,
+                                {});
+  }
+
+  librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx);
+  librbd::MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  InSequence seq;
+  expect_set_journal_policy(mock_image_ctx);
+  expect_open(mock_image_ctx, 0);
+  expect_acquire_lock(mock_image_ctx, 0);
+
+  expect_get_snap_namespace(mock_image_ctx, 1,
+                            cls::rbd::UserSnapshotNamespace{}, 0);
+  expect_get_snap_name(mock_image_ctx, 1, "snap1", 0);
+  expect_is_snap_protected(mock_image_ctx, 1, false, 0);
+  expect_start_op(mock_image_ctx, true);
+  expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap1",
+                     -EINVAL);
+
+  expect_close(mock_image_ctx, -EPERM);
+  expect_destroy(mock_image_ctx);
+
+  C_SaferCond ctx;
+  auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id,
+                                              &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageDeleterSnapshotPurgeRequest, CloseError) {
+  {
+    RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock);
+    m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1,
+                                0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0,
+                                {});
+  }
+
+  librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx);
+  librbd::MockExclusiveLock mock_exclusive_lock;
+  mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+
+  InSequence seq;
+  expect_set_journal_policy(mock_image_ctx);
+  expect_open(mock_image_ctx, 0);
+  expect_acquire_lock(mock_image_ctx, 0);
+
+  expect_get_snap_namespace(mock_image_ctx, 1,
+                            cls::rbd::UserSnapshotNamespace{}, 0);
+  expect_get_snap_name(mock_image_ctx, 1, "snap1", 0);
+  expect_is_snap_protected(mock_image_ctx, 1, false, 0);
+  expect_start_op(mock_image_ctx, true);
+  expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap1",
+                     0);
+
+  expect_close(mock_image_ctx, -EINVAL);
+  expect_destroy(mock_image_ctx);
+
+  C_SaferCond ctx;
+  auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id,
+                                              &ctx);
+  req->send();
+  ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace image_deleter
+} // namespace mirror
+} // namespace rbd
index 86d455518036cb30aa44d6e88da6c936a279157e..661ac44bff5e408aa0b19fdc79d8c16d7f34fe5b 100644 (file)
@@ -21,6 +21,8 @@ set(rbd_mirror_internal
   ServiceDaemon.cc
   Threads.cc
   types.cc
+  image_deleter/RemoveRequest.cc
+  image_deleter/SnapshotPurgeRequest.cc
   image_map/Action.cc
   image_map/LoadRequest.cc
   image_map/Policy.cc
index 333d11e06e494c8e5bfd0953b62ed548ecaa13d0..238a6085ef0132f46a54f356f50aec7912331600 100644 (file)
 #include "librbd/ImageState.h"
 #include "librbd/Journal.h"
 #include "librbd/Operations.h"
-#include "librbd/journal/Policy.h"
+#include "librbd/image/RemoveRequest.h"
 #include "cls/rbd/cls_rbd_client.h"
 #include "cls/rbd/cls_rbd_types.h"
 #include "librbd/Utils.h"
 #include "ImageDeleter.h"
+#include "tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h"
 
 #define dout_context g_ceph_context
 #define dout_subsys ceph_subsys_rbd_mirror
@@ -76,19 +77,6 @@ private:
   ImageDeleter<I> *image_del;
 };
 
-struct DeleteJournalPolicy : public librbd::journal::Policy {
-  bool append_disabled() const override {
-    return true;
-  }
-  bool journal_disabled() const override {
-    return false;
-  }
-
-  void allocate_tag_on_lock(Context *on_finish) override {
-    on_finish->complete(0);
-  }
-};
-
 } // anonymous namespace
 
 template <typename I>
@@ -365,85 +353,21 @@ bool ImageDeleter<I>::process_image_delete() {
   if (has_snapshots) {
     dout(20) << "local image has snapshots" << dendl;
 
-    ImageCtx *imgctx = new ImageCtx("", local_image_id, nullptr, ioctx, false);
-    r = imgctx->state->open(false);
-    if (r < 0) {
-      derr << "error opening image " << global_image_id << " ("
-           << local_image_id << "): " << cpp_strerror(r) << dendl;
-      enqueue_failed_delete(r);
-      return true;
-    }
-
-    {
-      RWLock::WLocker snap_locker(imgctx->snap_lock);
-      imgctx->set_journal_policy(new DeleteJournalPolicy());
-    }
+    C_SaferCond purge_ctx;
+    auto req = image_deleter::SnapshotPurgeRequest<I>::create(
+      ioctx, local_image_id, &purge_ctx);
+    req->send();
 
-    std::vector<librbd::snap_info_t> snaps;
-    r = librbd::snap_list(imgctx, snaps);
-    if (r < 0) {
-      derr << "error listing snapshot of image " << imgctx->name
-           << cpp_strerror(r) << dendl;
-      imgctx->state->close();
+    r = purge_ctx.wait();
+    if (r == -EBUSY) {
+      Mutex::Locker l(m_delete_lock);
+      m_active_delete->notify(r);
+      m_delete_queue.push_front(std::move(m_active_delete));
+      return false;
+    } else if (r < 0) {
       enqueue_failed_delete(r);
       return true;
     }
-
-    for (const auto& snap : snaps) {
-      dout(20) << "processing deletion of snapshot " << imgctx->name << "@"
-               << snap.name << dendl;
-
-      bool is_protected;
-      r = librbd::snap_is_protected(imgctx, snap.name.c_str(), &is_protected);
-      if (r < 0) {
-        derr << "error checking snapshot protection of snapshot "
-             << imgctx->name << "@" << snap.name << ": " << cpp_strerror(r)
-             << dendl;
-        imgctx->state->close();
-        enqueue_failed_delete(r);
-        return true;
-      }
-      if (is_protected) {
-        dout(20) << "snapshot " << imgctx->name << "@" << snap.name
-                 << " is protected, issuing unprotect command" << dendl;
-
-        r = imgctx->operations->snap_unprotect(
-          cls::rbd::UserSnapshotNamespace(), snap.name.c_str());
-        if (r == -EBUSY) {
-          // there are still clones of snapshots of this image, therefore send
-          // the delete request to the end of the queue
-          dout(10) << "local image id " << local_image_id << " has "
-                   << "snapshots with cloned children, postponing deletion..."
-                   << dendl;
-          imgctx->state->close();
-          Mutex::Locker l(m_delete_lock);
-          m_active_delete->notify(r);
-          m_delete_queue.push_front(std::move(m_active_delete));
-          return false;
-        } else if (r < 0) {
-          derr << "error unprotecting snapshot " << imgctx->name << "@"
-               << snap.name << ": " << cpp_strerror(r) << dendl;
-          imgctx->state->close();
-          enqueue_failed_delete(r);
-          return true;
-        }
-      }
-
-      r = imgctx->operations->snap_remove(cls::rbd::UserSnapshotNamespace(),
-                                         snap.name.c_str());
-      if (r < 0) {
-        derr << "error removing snapshot " << imgctx->name << "@"
-             << snap.name << ": " << cpp_strerror(r) << dendl;
-        imgctx->state->close();
-        enqueue_failed_delete(r);
-        return true;
-      }
-
-      dout(10) << "snapshot " << imgctx->name << "@" << snap.name
-               << " was deleted" << dendl;
-    }
-
-    imgctx->state->close();
   }
 
   librbd::NoOpProgressContext ctx;
diff --git a/src/tools/rbd_mirror/image_deleter/RemoveRequest.cc b/src/tools/rbd_mirror/image_deleter/RemoveRequest.cc
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc b/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc
new file mode 100644 (file)
index 0000000..8d2350d
--- /dev/null
@@ -0,0 +1,296 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h"
+#include "common/errno.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "librbd/Utils.h"
+#include "librbd/journal/Policy.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rbd_mirror
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd::mirror::image_deleter::SnapshotPurgeRequest: " \
+                           << this << " " << __func__ << ": "
+
+namespace rbd {
+namespace mirror {
+namespace image_deleter {
+
+namespace {
+
+struct DeleteJournalPolicy : public librbd::journal::Policy {
+  bool append_disabled() const override {
+    return true;
+  }
+  bool journal_disabled() const override {
+    return false;
+  }
+
+  void allocate_tag_on_lock(Context *on_finish) override {
+    on_finish->complete(0);
+  }
+};
+
+} // anonymous namespace
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+void SnapshotPurgeRequest<I>::send() {
+  open_image();
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::open_image() {
+  dout(10) << dendl;
+  m_image_ctx = I::create("", m_image_id, nullptr, m_io_ctx, false);
+
+  {
+    RWLock::WLocker snap_locker(m_image_ctx->snap_lock);
+    m_image_ctx->set_journal_policy(new DeleteJournalPolicy());
+  }
+
+  Context *ctx = create_context_callback<
+    SnapshotPurgeRequest<I>, &SnapshotPurgeRequest<I>::handle_open_image>(
+      this);
+  m_image_ctx->state->open(true, ctx);
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::handle_open_image(int r) {
+  dout(10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    derr << "failed to open image '" << m_image_id << "': " << cpp_strerror(r)
+         << dendl;
+    finish(r);
+    return;
+  }
+
+  acquire_lock();
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::acquire_lock() {
+  dout(10) << dendl;
+
+  m_image_ctx->owner_lock.get_read();
+  if (m_image_ctx->exclusive_lock == nullptr) {
+    m_image_ctx->owner_lock.put_read();
+
+    derr << "exclusive lock not enabled" << dendl;
+    m_ret_val = -EINVAL;
+    close_image();
+    return;
+  }
+
+  m_image_ctx->exclusive_lock->acquire_lock(create_context_callback<
+    SnapshotPurgeRequest<I>, &SnapshotPurgeRequest<I>::handle_acquire_lock>(
+      this));
+  m_image_ctx->owner_lock.put_read();
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::handle_acquire_lock(int r) {
+  dout(10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    derr << "failed to acquire exclusive lock: " << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+    close_image();
+    return;
+  }
+
+  {
+    RWLock::RLocker snap_locker(m_image_ctx->snap_lock);
+    m_snaps = m_image_ctx->snaps;
+  }
+  snap_unprotect();
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::snap_unprotect() {
+  if (m_snaps.empty()) {
+    close_image();
+    return;
+  }
+
+  librados::snap_t snap_id = m_snaps.back();
+  m_image_ctx->snap_lock.get_read();
+  int r = m_image_ctx->get_snap_namespace(snap_id, &m_snap_namespace);
+  if (r < 0) {
+    m_image_ctx->snap_lock.put_read();
+
+    derr << "failed to get snap namespace: " << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+    close_image();
+    return;
+  }
+
+  r = m_image_ctx->get_snap_name(snap_id, &m_snap_name);
+  if (r < 0) {
+    m_image_ctx->snap_lock.put_read();
+
+    derr << "failed to get snap name: " << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+    close_image();
+    return;
+  }
+
+  bool is_protected;
+  r = m_image_ctx->is_snap_protected(snap_id, &is_protected);
+  if (r < 0) {
+    m_image_ctx->snap_lock.put_read();
+
+    derr << "failed to get snap protection status: " << cpp_strerror(r)
+         << dendl;
+    m_ret_val = r;
+    close_image();
+    return;
+  }
+  m_image_ctx->snap_lock.put_read();
+
+  if (!is_protected) {
+    snap_remove();
+    return;
+  }
+
+  dout(10) << "snap_id=" << snap_id << ", "
+           << "snap_namespace=" << m_snap_namespace << ", "
+           << "snap_name=" << m_snap_name << dendl;
+
+  auto finish_op_ctx = start_lock_op();
+  if (finish_op_ctx == nullptr) {
+    derr << "lost exclusive lock" << dendl;
+    m_ret_val = -EROFS;
+    close_image();
+    return;
+  }
+
+  auto ctx = new FunctionContext([this, finish_op_ctx](int r) {
+      handle_snap_unprotect(r);
+      finish_op_ctx->complete(0);
+    });
+  RWLock::RLocker owner_locker(m_image_ctx->owner_lock);
+  m_image_ctx->operations->execute_snap_unprotect(
+    m_snap_namespace, m_snap_name.c_str(), ctx);
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::handle_snap_unprotect(int r) {
+  dout(10) << "r=" << r << dendl;
+
+  if (r == -EBUSY) {
+    dout(10) << "snapshot in-use" << dendl;
+    m_ret_val = r;
+    close_image();
+    return;
+  } else if (r < 0) {
+    derr << "failed to unprotect snapshot: " << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+    close_image();
+    return;
+  }
+
+  {
+    // avoid the need to refresh to delete the newly unprotected snapshot
+    RWLock::RLocker snap_locker(m_image_ctx->snap_lock);
+    librados::snap_t snap_id = m_snaps.back();
+    auto snap_info_it = m_image_ctx->snap_info.find(snap_id);
+    if (snap_info_it != m_image_ctx->snap_info.end()) {
+      snap_info_it->second.protection_status =
+        RBD_PROTECTION_STATUS_UNPROTECTED;
+    }
+  }
+
+  snap_remove();
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::snap_remove() {
+  librados::snap_t snap_id = m_snaps.back();
+  dout(10) << "snap_id=" << snap_id << ", "
+           << "snap_namespace=" << m_snap_namespace << ", "
+           << "snap_name=" << m_snap_name << dendl;
+
+  auto finish_op_ctx = start_lock_op();
+  if (finish_op_ctx == nullptr) {
+    derr << "lost exclusive lock" << dendl;
+    m_ret_val = -EROFS;
+    close_image();
+    return;
+  }
+
+  auto ctx = new FunctionContext([this, finish_op_ctx](int r) {
+      handle_snap_remove(r);
+      finish_op_ctx->complete(0);
+    });
+  RWLock::RLocker owner_locker(m_image_ctx->owner_lock);
+  m_image_ctx->operations->execute_snap_remove(
+    m_snap_namespace, m_snap_name.c_str(), ctx);
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::handle_snap_remove(int r) {
+  dout(10) << "r=" << r << dendl;
+
+  if (r < 0) {
+    derr << "failed to remove snapshot: " << cpp_strerror(r) << dendl;
+    m_ret_val = r;
+    close_image();
+    return;
+  }
+
+  m_snaps.pop_back();
+  snap_unprotect();
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::close_image() {
+  dout(10) << dendl;
+
+  m_image_ctx->state->close(create_context_callback<
+    SnapshotPurgeRequest<I>,
+    &SnapshotPurgeRequest<I>::handle_close_image>(this));
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::handle_close_image(int r) {
+  dout(10) << "r=" << r << dendl;
+
+  m_image_ctx->destroy();
+  m_image_ctx = nullptr;
+
+  if (r < 0) {
+    derr << "failed to close: " << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+  finish(0);
+}
+
+template <typename I>
+void SnapshotPurgeRequest<I>::finish(int r) {
+  if (m_ret_val < 0) {
+    r = m_ret_val;
+  }
+
+  m_on_finish->complete(r);
+  delete this;
+}
+
+template <typename I>
+Context *SnapshotPurgeRequest<I>::start_lock_op() {
+  RWLock::RLocker owner_locker(m_image_ctx->owner_lock);
+  return m_image_ctx->exclusive_lock->start_op();
+}
+
+} // namespace image_deleter
+} // namespace mirror
+} // namespace rbd
+
+template class rbd::mirror::image_deleter::SnapshotPurgeRequest<librbd::ImageCtx>;
diff --git a/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h b/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h
new file mode 100644 (file)
index 0000000..59a13cb
--- /dev/null
@@ -0,0 +1,104 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RBD_MIRROR_IMAGE_DELETER_SNAPSHOT_PURGE_REQUEST_H
+#define CEPH_RBD_MIRROR_IMAGE_DELETER_SNAPSHOT_PURGE_REQUEST_H
+
+#include "include/rados/librados.hpp"
+#include "cls/rbd/cls_rbd_types.h"
+#include <string>
+#include <vector>
+
+class Context;
+namespace librbd { struct ImageCtx; }
+
+namespace rbd {
+namespace mirror {
+namespace image_deleter {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class SnapshotPurgeRequest {
+public:
+  static SnapshotPurgeRequest* create(librados::IoCtx &io_ctx,
+                                      const std::string &image_id,
+                                      Context *on_finish) {
+    return new SnapshotPurgeRequest(io_ctx, image_id, on_finish);
+  }
+
+  SnapshotPurgeRequest(librados::IoCtx &io_ctx, const std::string &image_id,
+                       Context *on_finish)
+    : m_io_ctx(io_ctx), m_image_id(image_id), m_on_finish(on_finish) {
+  }
+
+  void send();
+
+private:
+  /*
+   * @verbatim
+   *
+   * <start>
+   *    |
+   *    v
+   * OPEN_IMAGE
+   *    |
+   *    v
+   * ACQUIRE_LOCK
+   *    |
+   *    | (repeat for each snapshot)
+   *    |/------------------------\
+   *    |                         |
+   *    v (skip if not needed)    |
+   * SNAP_UNPROTECT               |
+   *    |                         |
+   *    v (skip if not needed)    |
+   * SNAP_REMOVE -----------------/
+   *    |
+   *    v
+   * CLOSE_IMAGE
+   *    |
+   *    v
+   * <finish>
+   *
+   * @endverbatim
+   */
+
+  librados::IoCtx &m_io_ctx;
+  std::string m_image_id;
+  Context *m_on_finish;
+
+  ImageCtxT *m_image_ctx = nullptr;
+  int m_ret_val = 0;
+
+  std::vector<librados::snap_t> m_snaps;
+  cls::rbd::SnapshotNamespace m_snap_namespace;
+  std::string m_snap_name;
+
+  void open_image();
+  void handle_open_image(int r);
+
+  void acquire_lock();
+  void handle_acquire_lock(int r);
+
+  void snap_unprotect();
+  void handle_snap_unprotect(int r);
+
+  void snap_remove();
+  void handle_snap_remove(int r);
+
+  void close_image();
+  void handle_close_image(int r);
+
+  void finish(int r);
+
+  Context *start_lock_op();
+
+};
+
+} // namespace image_deleter
+} // namespace mirror
+} // namespace rbd
+
+extern template class rbd::mirror::image_deleter::SnapshotPurgeRequest<librbd::ImageCtx>;
+
+#endif // CEPH_RBD_MIRROR_IMAGE_DELETER_SNAPSHOT_PURGE_REQUEST_H
+