From: Jason Dillaman Date: Thu, 16 Nov 2017 20:34:04 +0000 (-0500) Subject: rbd-mirror: move snap purge to standalone state machine X-Git-Tag: v13.0.1~134^2~4 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c79d74fe5291b5face8fe2dd86ba0494df26a892;p=ceph.git rbd-mirror: move snap purge to standalone state machine Signed-off-by: Jason Dillaman --- diff --git a/src/test/rbd_mirror/CMakeLists.txt b/src/test/rbd_mirror/CMakeLists.txt index 6d4e4cfa5014..48f2e249f942 100644 --- a/src/test/rbd_mirror/CMakeLists.txt +++ b/src/test/rbd_mirror/CMakeLists.txt @@ -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 index 000000000000..bf312135d60a --- /dev/null +++ b/src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc @@ -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 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(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 diff --git a/src/tools/rbd_mirror/CMakeLists.txt b/src/tools/rbd_mirror/CMakeLists.txt index 86d455518036..661ac44bff5e 100644 --- a/src/tools/rbd_mirror/CMakeLists.txt +++ b/src/tools/rbd_mirror/CMakeLists.txt @@ -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 diff --git a/src/tools/rbd_mirror/ImageDeleter.cc b/src/tools/rbd_mirror/ImageDeleter.cc index 333d11e06e49..238a6085ef01 100644 --- a/src/tools/rbd_mirror/ImageDeleter.cc +++ b/src/tools/rbd_mirror/ImageDeleter.cc @@ -29,11 +29,12 @@ #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 *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 @@ -365,85 +353,21 @@ bool ImageDeleter::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::create( + ioctx, local_image_id, &purge_ctx); + req->send(); - std::vector 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 index 000000000000..e69de29bb2d1 diff --git a/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc b/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc new file mode 100644 index 000000000000..8d2350decf3a --- /dev/null +++ b/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc @@ -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 +void SnapshotPurgeRequest::send() { + open_image(); +} + +template +void SnapshotPurgeRequest::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, &SnapshotPurgeRequest::handle_open_image>( + this); + m_image_ctx->state->open(true, ctx); +} + +template +void SnapshotPurgeRequest::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 +void SnapshotPurgeRequest::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, &SnapshotPurgeRequest::handle_acquire_lock>( + this)); + m_image_ctx->owner_lock.put_read(); +} + +template +void SnapshotPurgeRequest::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 +void SnapshotPurgeRequest::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 +void SnapshotPurgeRequest::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 +void SnapshotPurgeRequest::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 +void SnapshotPurgeRequest::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 +void SnapshotPurgeRequest::close_image() { + dout(10) << dendl; + + m_image_ctx->state->close(create_context_callback< + SnapshotPurgeRequest, + &SnapshotPurgeRequest::handle_close_image>(this)); +} + +template +void SnapshotPurgeRequest::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 +void SnapshotPurgeRequest::finish(int r) { + if (m_ret_val < 0) { + r = m_ret_val; + } + + m_on_finish->complete(r); + delete this; +} + +template +Context *SnapshotPurgeRequest::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; diff --git a/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h b/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h new file mode 100644 index 000000000000..59a13cb8f448 --- /dev/null +++ b/src/tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h @@ -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 +#include + +class Context; +namespace librbd { struct ImageCtx; } + +namespace rbd { +namespace mirror { +namespace image_deleter { + +template +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 + * + * + * | + * 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 + * + * + * @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 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; + +#endif // CEPH_RBD_MIRROR_IMAGE_DELETER_SNAPSHOT_PURGE_REQUEST_H +