From 334304454a9ed260b5f5d00698575117365cd5cf Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Thu, 13 Feb 2020 16:41:03 -0500 Subject: [PATCH] rbd-mirror: state machine to apply any image-state deltas When replaying mirror snapshots from a remote image, the image state tracks the current state of the image at the image the snapshot was created. Therefore, the new state machine will apply any discovered deltas between the remote image state and the local image. Signed-off-by: Jason Dillaman --- src/test/rbd_mirror/CMakeLists.txt | 1 + .../test_mock_ApplyImageStateRequest.cc | 640 +++++++++++++++++ src/tools/rbd_mirror/CMakeLists.txt | 1 + .../snapshot/ApplyImageStateRequest.cc | 677 ++++++++++++++++++ .../snapshot/ApplyImageStateRequest.h | 155 ++++ 5 files changed, 1474 insertions(+) create mode 100644 src/test/rbd_mirror/image_replayer/snapshot/test_mock_ApplyImageStateRequest.cc create mode 100644 src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.cc create mode 100644 src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.h diff --git a/src/test/rbd_mirror/CMakeLists.txt b/src/test/rbd_mirror/CMakeLists.txt index 0479a43c27f..36a1603d9c5 100644 --- a/src/test/rbd_mirror/CMakeLists.txt +++ b/src/test/rbd_mirror/CMakeLists.txt @@ -42,6 +42,7 @@ add_executable(unittest_rbd_mirror image_replayer/journal/test_mock_PrepareReplayRequest.cc image_replayer/journal/test_mock_EventPreprocessor.cc image_replayer/journal/test_mock_Replayer.cc + image_replayer/snapshot/test_mock_ApplyImageStateRequest.cc image_replayer/snapshot/test_mock_CreateLocalImageRequest.cc image_replayer/snapshot/test_mock_Replayer.cc image_sync/test_mock_SyncPointCreateRequest.cc diff --git a/src/test/rbd_mirror/image_replayer/snapshot/test_mock_ApplyImageStateRequest.cc b/src/test/rbd_mirror/image_replayer/snapshot/test_mock_ApplyImageStateRequest.cc new file mode 100644 index 00000000000..303aedb2cdc --- /dev/null +++ b/src/test/rbd_mirror/image_replayer/snapshot/test_mock_ApplyImageStateRequest.cc @@ -0,0 +1,640 @@ +// -*- 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/internal.h" +#include "librbd/Operations.h" +#include "librbd/image/GetMetadataRequest.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template <> +struct GetMetadataRequest { + std::map* pairs = nullptr; + Context* on_finish = nullptr; + + static GetMetadataRequest* s_instance; + static GetMetadataRequest* create(librados::IoCtx& io_ctx, + const std::string& oid, + const std::string& filter, + const std::string& last_key, + size_t max_results, + std::map* pairs, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->pairs = pairs; + s_instance->on_finish = on_finish; + return s_instance; + } + + GetMetadataRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +GetMetadataRequest* GetMetadataRequest::s_instance = nullptr; + +} // namespace image +} // namespace librbd + +#include "tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.cc" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +namespace rbd { +namespace mirror { +namespace image_replayer { +namespace snapshot { + +class TestMockImageReplayerSnapshotApplyImageStateRequest : public TestMockFixture { +public: + typedef ApplyImageStateRequest MockApplyImageStateRequest; + typedef librbd::image::GetMetadataRequest MockGetMetadataRequest; + + 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)); + + ASSERT_EQ(0, create_image(rbd, m_remote_io_ctx, m_image_name, m_image_size)); + ASSERT_EQ(0, open_image(m_remote_io_ctx, m_image_name, &m_remote_image_ctx)); + + m_mock_local_image_ctx = new librbd::MockTestImageCtx(*m_local_image_ctx); + m_mock_remote_image_ctx = new librbd::MockTestImageCtx(*m_remote_image_ctx); + } + + void TearDown() override { + delete m_mock_remote_image_ctx; + delete m_mock_local_image_ctx; + TestMockFixture::TearDown(); + } + + void expect_rename_image(const std::string& name, int r) { + EXPECT_CALL(*m_mock_local_image_ctx->operations, execute_rename(name, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_update_features(uint64_t features, bool enable, int r) { + EXPECT_CALL(*m_mock_local_image_ctx->operations, + execute_update_features(features, enable, _, 0U)) + .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_get_metadata(MockGetMetadataRequest& mock_get_metadata_request, + const std::map& pairs, + int r) { + EXPECT_CALL(mock_get_metadata_request, send()) + .WillOnce(Invoke([this, &mock_get_metadata_request, pairs, r]() { + *mock_get_metadata_request.pairs = pairs; + m_threads->work_queue->queue(mock_get_metadata_request.on_finish, r); + })); + } + + void expect_update_metadata(const std::vector& remove, + const std::map& pairs, + int r) { + for (auto& key : remove) { + bufferlist bl; + ceph::encode(key, bl); + EXPECT_CALL(get_mock_io_ctx(m_mock_local_image_ctx->md_ctx), + exec(m_mock_local_image_ctx->header_oid, _, StrEq("rbd"), + StrEq("metadata_remove"), ContentsEqual(bl), _, _)) + .WillOnce(Return(r)); + if (r < 0) { + return; + } + } + + if (!pairs.empty()) { + bufferlist bl; + ceph::encode(pairs, bl); + EXPECT_CALL(get_mock_io_ctx(m_mock_local_image_ctx->md_ctx), + exec(m_mock_local_image_ctx->header_oid, _, StrEq("rbd"), + StrEq("metadata_set"), ContentsEqual(bl), _, _)) + .WillOnce(Return(r)); + } + } + + void expect_unprotect_snapshot(const std::string& name, int r) { + EXPECT_CALL(*m_mock_local_image_ctx->operations, + execute_snap_unprotect({cls::rbd::UserSnapshotNamespace{}}, + name, _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_remove_snapshot(const std::string& name, int r) { + EXPECT_CALL(*m_mock_local_image_ctx->operations, + execute_snap_remove({cls::rbd::UserSnapshotNamespace{}}, + name, _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_protect_snapshot(const std::string& name, int r) { + EXPECT_CALL(*m_mock_local_image_ctx->operations, + execute_snap_protect({cls::rbd::UserSnapshotNamespace{}}, + name, _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_rename_snapshot(uint64_t snap_id, const std::string& name, + int r) { + EXPECT_CALL(*m_mock_local_image_ctx->operations, + execute_snap_rename(snap_id, name, _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + + void expect_set_snap_limit(uint64_t limit, int r) { + EXPECT_CALL(*m_mock_local_image_ctx->operations, + execute_snap_set_limit(limit, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + librbd::ImageCtx *m_local_image_ctx; + librbd::ImageCtx *m_remote_image_ctx; + + librbd::MockTestImageCtx *m_mock_local_image_ctx = nullptr; + librbd::MockTestImageCtx *m_mock_remote_image_ctx = nullptr; + +}; + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, NoChanges) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, RenameImage) { + InSequence seq; + + expect_rename_image("new name", 0); + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = "new name"; + image_state.features = m_remote_image_ctx->features; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, RenameImageError) { + InSequence seq; + + expect_rename_image("new name", -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = "new name"; + image_state.features = m_remote_image_ctx->features; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, UpdateFeatures) { + InSequence seq; + + expect_update_features(RBD_FEATURE_DEEP_FLATTEN, false, 0); + expect_update_features(RBD_FEATURE_OBJECT_MAP, true, 0); + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_OBJECT_MAP; + m_mock_local_image_ctx->features = RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_DEEP_FLATTEN; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, UpdateFeaturesError) { + InSequence seq; + + expect_update_features(RBD_FEATURE_DEEP_FLATTEN, false, -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_OBJECT_MAP; + m_mock_local_image_ctx->features = RBD_FEATURE_EXCLUSIVE_LOCK | + RBD_FEATURE_DEEP_FLATTEN; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, UpdateImageMeta) { + InSequence seq; + + bufferlist data_bl; + ceph::encode("data", data_bl); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, + {{"key1", {}}, {"key2", {}}}, 0); + expect_update_metadata({"key2"}, {{"key1", data_bl}}, 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.metadata = {{"key1", data_bl}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, GetImageMetaError) { + InSequence seq; + + bufferlist data_bl; + ceph::encode("data", data_bl); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, + {{"key1", {}}, {"key2", {}}}, -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.metadata = {{"key1", data_bl}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, UpdateImageMetaError) { + InSequence seq; + + bufferlist data_bl; + ceph::encode("data", data_bl); + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, + {{"key1", {}}, {"key2", {}}}, 0); + expect_update_metadata({"key2"}, {{"key1", data_bl}}, -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.metadata = {{"key1", data_bl}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, UnprotectSnapshot) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_unprotect_snapshot("snap1", 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.snapshots = { + {1U, {cls::rbd::UserSnapshotNamespace{}, "snap1", + RBD_PROTECTION_STATUS_UNPROTECTED}}}; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, UnprotectSnapshotError) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_unprotect_snapshot("snap1", -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.snapshots = { + {1U, {cls::rbd::UserSnapshotNamespace{}, "snap1", + RBD_PROTECTION_STATUS_UNPROTECTED}}}; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, RemoveSnapshot) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_remove_snapshot("snap1", 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, RemoveSnapshotError) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_remove_snapshot("snap1", -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, ProtectSnapshot) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_protect_snapshot("snap1", 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.snapshots = { + {1U, {cls::rbd::UserSnapshotNamespace{}, "snap1", + RBD_PROTECTION_STATUS_PROTECTED}}}; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, ProtectSnapshotError) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_protect_snapshot("snap1", -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.snapshots = { + {1U, {cls::rbd::UserSnapshotNamespace{}, "snap1", + RBD_PROTECTION_STATUS_PROTECTED}}}; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, RenameSnapshot) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_rename_snapshot(11, "snap1-renamed", 0); + + expect_set_snap_limit(0, 0); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.snapshots = { + {1U, {cls::rbd::UserSnapshotNamespace{}, "snap1-renamed", + RBD_PROTECTION_STATUS_PROTECTED}}}; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, RenameSnapshotError) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_rename_snapshot(11, "snap1-renamed", -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + image_state.snapshots = { + {1U, {cls::rbd::UserSnapshotNamespace{}, "snap1-renamed", + RBD_PROTECTION_STATUS_PROTECTED}}}; + m_mock_local_image_ctx->snap_info = { + {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{}, + 0U, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {}}}, + {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{ + cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid", + 1, true, 0, {{1, 11}, {2, CEPH_NOSNAP}}}, + 0, {}, 0, 0, {}}}}; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerSnapshotApplyImageStateRequest, SetSnapshotLimitError) { + InSequence seq; + + MockGetMetadataRequest mock_get_metadata_request; + expect_get_metadata(mock_get_metadata_request, {}, 0); + + expect_set_snap_limit(0, -EINVAL); + + librbd::mirror::snapshot::ImageState image_state; + image_state.name = m_image_name; + image_state.features = m_remote_image_ctx->features; + + C_SaferCond ctx; + auto req = MockApplyImageStateRequest::create( + "local mirror uuid", "remote mirror uuid", m_mock_local_image_ctx, + m_mock_remote_image_ctx, image_state, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace journal +} // namespace image_replayer +} // namespace mirror +} // namespace rbd diff --git a/src/tools/rbd_mirror/CMakeLists.txt b/src/tools/rbd_mirror/CMakeLists.txt index 6140e3e62ee..1c288c9cc5f 100644 --- a/src/tools/rbd_mirror/CMakeLists.txt +++ b/src/tools/rbd_mirror/CMakeLists.txt @@ -51,6 +51,7 @@ set(rbd_mirror_internal image_replayer/journal/ReplayStatusFormatter.cc image_replayer/journal/StateBuilder.cc image_replayer/journal/SyncPointHandler.cc + image_replayer/snapshot/ApplyImageStateRequest.cc image_replayer/snapshot/CreateLocalImageRequest.cc image_replayer/snapshot/PrepareReplayRequest.cc image_replayer/snapshot/Replayer.cc diff --git a/src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.cc b/src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.cc new file mode 100644 index 00000000000..3d628efd710 --- /dev/null +++ b/src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.cc @@ -0,0 +1,677 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "ApplyImageStateRequest.h" +#include "common/debug.h" +#include "common/errno.h" +#include "cls/rbd/cls_rbd_client.h" +#include "librbd/ImageCtx.h" +#include "librbd/Operations.h" +#include "librbd/Utils.h" +#include "librbd/image/GetMetadataRequest.h" + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_rbd_mirror +#undef dout_prefix +#define dout_prefix *_dout << "rbd::mirror::image_replayer::snapshot::" \ + << "ApplyImageStateRequest: " << this << " " \ + << __func__ << ": " + +namespace rbd { +namespace mirror { +namespace image_replayer { +namespace snapshot { + +using librbd::util::create_context_callback; +using librbd::util::create_rados_callback; + +template +ApplyImageStateRequest::ApplyImageStateRequest( + const std::string& local_mirror_uuid, + const std::string& remote_mirror_uuid, + I* local_image_ctx, + I* remote_image_ctx, + librbd::mirror::snapshot::ImageState image_state, + Context* on_finish) + : m_local_mirror_uuid(local_mirror_uuid), + m_remote_mirror_uuid(remote_mirror_uuid), + m_local_image_ctx(local_image_ctx), + m_remote_image_ctx(remote_image_ctx), + m_image_state(image_state), + m_on_finish(on_finish) { + dout(15) << "image_state=" << m_image_state << dendl; + + std::shared_lock image_locker{m_local_image_ctx->image_lock}; + m_features = m_local_image_ctx->features & ~RBD_FEATURES_IMPLICIT_ENABLE; + compute_local_to_remote_snap_ids(); +} + +template +void ApplyImageStateRequest::send() { + rename_image(); +} + +template +void ApplyImageStateRequest::rename_image() { + std::shared_lock owner_locker{m_local_image_ctx->owner_lock}; + std::shared_lock image_locker{m_local_image_ctx->image_lock}; + if (m_local_image_ctx->name == m_image_state.name) { + image_locker.unlock(); + owner_locker.unlock(); + + update_features(); + return; + } + image_locker.unlock(); + + dout(15) << "local_image_name=" << m_local_image_ctx->name << ", " + << "remote_image_name=" << m_image_state.name << dendl; + + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_rename_image>(this); + m_local_image_ctx->operations->execute_rename(m_image_state.name, ctx); +} + +template +void ApplyImageStateRequest::handle_rename_image(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to rename image to '" << m_image_state.name << "': " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + + update_features(); +} + +template +void ApplyImageStateRequest::update_features() { + uint64_t feature_updates = 0UL; + bool enabled = false; + + auto image_state_features = + m_image_state.features & ~RBD_FEATURES_IMPLICIT_ENABLE; + feature_updates = (m_features & ~image_state_features); + if (feature_updates == 0UL) { + feature_updates = (image_state_features & ~m_features); + enabled = (feature_updates != 0UL); + } + + if (feature_updates == 0UL) { + get_image_meta(); + return; + } + + dout(15) << "image_features=" << m_features << ", " + << "state_features=" << image_state_features << ", " + << "feature_updates=" << feature_updates << ", " + << "enabled=" << enabled << dendl; + + if (enabled) { + m_features |= feature_updates; + } else { + m_features &= ~feature_updates; + } + + std::shared_lock owner_lock{m_local_image_ctx->owner_lock}; + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_update_features>(this); + m_local_image_ctx->operations->execute_update_features( + feature_updates, enabled, ctx, 0U); +} + +template +void ApplyImageStateRequest::handle_update_features(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to update image features: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + update_features(); +} + +template +void ApplyImageStateRequest::get_image_meta() { + dout(15) << dendl; + + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_get_image_meta>(this); + auto req = librbd::image::GetMetadataRequest::create( + m_local_image_ctx->md_ctx, m_local_image_ctx->header_oid, "", "", 0U, + &m_metadata, ctx); + req->send(); +} + +template +void ApplyImageStateRequest::handle_get_image_meta(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to fetch local image metadata: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + update_image_meta(); +} + +template +void ApplyImageStateRequest::update_image_meta() { + std::set keys_to_remove; + for (const auto& [key, value] : m_metadata) { + if (m_image_state.metadata.count(key) == 0) { + dout(15) << "removing image-meta key '" << key << "'" << dendl; + keys_to_remove.insert(key); + } + } + + std::map metadata_to_update; + for (const auto& [key, value] : m_image_state.metadata) { + auto it = m_metadata.find(key); + if (it == m_metadata.end() || !it->second.contents_equal(value)) { + dout(15) << "updating image-meta key '" << key << "'" << dendl; + metadata_to_update.insert({key, value}); + } + } + + if (keys_to_remove.empty() && metadata_to_update.empty()) { + unprotect_snapshot(); + return; + } + + dout(15) << dendl; + + librados::ObjectWriteOperation op; + for (const auto& key : keys_to_remove) { + librbd::cls_client::metadata_remove(&op, key); + } + if (!metadata_to_update.empty()) { + librbd::cls_client::metadata_set(&op, metadata_to_update); + } + + auto aio_comp = create_rados_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_update_image_meta>(this); + int r = m_local_image_ctx->md_ctx.aio_operate(m_local_image_ctx->header_oid, aio_comp, + &op); + ceph_assert(r == 0); + aio_comp->release(); +} + +template +void ApplyImageStateRequest::handle_update_image_meta(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to update image metadata: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + m_metadata.clear(); + + m_prev_snap_id = CEPH_NOSNAP; + unprotect_snapshot(); +} + +template +void ApplyImageStateRequest::unprotect_snapshot() { + std::shared_lock image_locker{m_local_image_ctx->image_lock}; + + auto snap_it = m_local_image_ctx->snap_info.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_it = m_local_image_ctx->snap_info.upper_bound(m_prev_snap_id); + } + + for (; snap_it != m_local_image_ctx->snap_info.end(); ++snap_it) { + auto snap_id = snap_it->first; + const auto& snap_info = snap_it->second; + + auto user_ns = boost::get( + &snap_info.snap_namespace); + if (user_ns == nullptr) { + dout(20) << "snapshot " << snap_id << " is not a user snapshot" << dendl; + continue; + } + + if (snap_info.protection_status == RBD_PROTECTION_STATUS_UNPROTECTED) { + dout(20) << "snapshot " << snap_id << " is already unprotected" << dendl; + continue; + } + + auto snap_id_map_it = m_local_to_remote_snap_ids.find(snap_id); + if (snap_id_map_it == m_local_to_remote_snap_ids.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image" + << dendl; + break; + } + + auto remote_snap_id = snap_id_map_it->second; + auto snap_state_it = m_image_state.snapshots.find(remote_snap_id); + if (snap_state_it == m_image_state.snapshots.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image " + << "state" << dendl; + break; + } + + const auto& snap_state = snap_state_it->second; + if (snap_state.protection_status == RBD_PROTECTION_STATUS_UNPROTECTED) { + dout(15) << "snapshot " << snap_id << " is unprotected in remote image" + << dendl; + break; + } + } + + if (snap_it == m_local_image_ctx->snap_info.end()) { + image_locker.unlock(); + + // no local snapshots to unprotect + m_prev_snap_id = CEPH_NOSNAP; + remove_snapshot(); + return; + } + + m_prev_snap_id = snap_it->first; + m_snap_name = snap_it->second.name; + image_locker.unlock(); + + dout(15) << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << dendl; + + std::shared_lock owner_locker{m_local_image_ctx->owner_lock}; + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_unprotect_snapshot>(this); + m_local_image_ctx->operations->execute_snap_unprotect( + cls::rbd::UserSnapshotNamespace{}, m_snap_name.c_str(), ctx); +} + +template +void ApplyImageStateRequest::handle_unprotect_snapshot(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to unprotect snapshot " << m_snap_name << ": " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + + unprotect_snapshot(); +} + +template +void ApplyImageStateRequest::remove_snapshot() { + std::shared_lock image_locker{m_local_image_ctx->image_lock}; + + auto snap_it = m_local_image_ctx->snap_info.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_it = m_local_image_ctx->snap_info.upper_bound(m_prev_snap_id); + } + + for (; snap_it != m_local_image_ctx->snap_info.end(); ++snap_it) { + auto snap_id = snap_it->first; + const auto& snap_info = snap_it->second; + + auto user_ns = boost::get( + &snap_info.snap_namespace); + if (user_ns == nullptr) { + dout(20) << "snapshot " << snap_id << " is not a user snapshot" << dendl; + continue; + } + + auto snap_id_map_it = m_local_to_remote_snap_ids.find(snap_id); + if (snap_id_map_it == m_local_to_remote_snap_ids.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image" + << dendl; + break; + } + + auto remote_snap_id = snap_id_map_it->second; + auto snap_state_it = m_image_state.snapshots.find(remote_snap_id); + if (snap_state_it == m_image_state.snapshots.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image " + << "state" << dendl; + break; + } + } + + if (snap_it == m_local_image_ctx->snap_info.end()) { + image_locker.unlock(); + + // no local snapshots to remove + m_prev_snap_id = CEPH_NOSNAP; + protect_snapshot(); + return; + } + + m_prev_snap_id = snap_it->first; + m_snap_name = snap_it->second.name; + image_locker.unlock(); + + dout(15) << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << dendl; + + std::shared_lock owner_locker{m_local_image_ctx->owner_lock}; + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_remove_snapshot>(this); + m_local_image_ctx->operations->execute_snap_remove( + cls::rbd::UserSnapshotNamespace{}, m_snap_name.c_str(), ctx); +} + +template +void ApplyImageStateRequest::handle_remove_snapshot(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to remove snapshot " << m_snap_name << ": " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + + remove_snapshot(); +} + +template +void ApplyImageStateRequest::protect_snapshot() { + std::shared_lock image_locker{m_local_image_ctx->image_lock}; + + auto snap_it = m_local_image_ctx->snap_info.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_it = m_local_image_ctx->snap_info.upper_bound(m_prev_snap_id); + } + + for (; snap_it != m_local_image_ctx->snap_info.end(); ++snap_it) { + auto snap_id = snap_it->first; + const auto& snap_info = snap_it->second; + + auto user_ns = boost::get( + &snap_info.snap_namespace); + if (user_ns == nullptr) { + dout(20) << "snapshot " << snap_id << " is not a user snapshot" << dendl; + continue; + } + + if (snap_info.protection_status == RBD_PROTECTION_STATUS_PROTECTED) { + dout(20) << "snapshot " << snap_id << " is already protected" << dendl; + continue; + } + + auto snap_id_map_it = m_local_to_remote_snap_ids.find(snap_id); + if (snap_id_map_it == m_local_to_remote_snap_ids.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image" + << dendl; + continue; + } + + auto remote_snap_id = snap_id_map_it->second; + auto snap_state_it = m_image_state.snapshots.find(remote_snap_id); + if (snap_state_it == m_image_state.snapshots.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image " + << "state" << dendl; + continue; + } + + const auto& snap_state = snap_state_it->second; + if (snap_state.protection_status == RBD_PROTECTION_STATUS_PROTECTED) { + dout(15) << "snapshot " << snap_id << " is protected in remote image" + << dendl; + break; + } + } + + if (snap_it == m_local_image_ctx->snap_info.end()) { + image_locker.unlock(); + + // no local snapshots to protect + m_prev_snap_id = CEPH_NOSNAP; + rename_snapshot(); + return; + } + + m_prev_snap_id = snap_it->first; + m_snap_name = snap_it->second.name; + image_locker.unlock(); + + dout(15) << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << dendl; + + std::shared_lock owner_locker{m_local_image_ctx->owner_lock}; + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_protect_snapshot>(this); + m_local_image_ctx->operations->execute_snap_protect( + cls::rbd::UserSnapshotNamespace{}, m_snap_name.c_str(), ctx); +} + +template +void ApplyImageStateRequest::handle_protect_snapshot(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to protect snapshot " << m_snap_name << ": " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + + protect_snapshot(); +} + +template +void ApplyImageStateRequest::rename_snapshot() { + std::shared_lock image_locker{m_local_image_ctx->image_lock}; + + auto snap_it = m_local_image_ctx->snap_info.begin(); + if (m_prev_snap_id != CEPH_NOSNAP) { + snap_it = m_local_image_ctx->snap_info.upper_bound(m_prev_snap_id); + } + + for (; snap_it != m_local_image_ctx->snap_info.end(); ++snap_it) { + auto snap_id = snap_it->first; + const auto& snap_info = snap_it->second; + + auto user_ns = boost::get( + &snap_info.snap_namespace); + if (user_ns == nullptr) { + dout(20) << "snapshot " << snap_id << " is not a user snapshot" << dendl; + continue; + } + + auto snap_id_map_it = m_local_to_remote_snap_ids.find(snap_id); + if (snap_id_map_it == m_local_to_remote_snap_ids.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image" + << dendl; + continue; + } + + auto remote_snap_id = snap_id_map_it->second; + auto snap_state_it = m_image_state.snapshots.find(remote_snap_id); + if (snap_state_it == m_image_state.snapshots.end()) { + dout(15) << "snapshot " << snap_id << " does not exist in remote image " + << "state" << dendl; + continue; + } + + const auto& snap_state = snap_state_it->second; + if (snap_info.name != snap_state.name) { + dout(15) << "snapshot " << snap_id << " has been renamed from '" + << snap_info.name << "' to '" << snap_state.name << "'" + << dendl; + m_snap_name = snap_state.name; + break; + } + } + + if (snap_it == m_local_image_ctx->snap_info.end()) { + image_locker.unlock(); + + // no local snapshots to protect + m_prev_snap_id = CEPH_NOSNAP; + set_snapshot_limit(); + return; + } + + m_prev_snap_id = snap_it->first; + image_locker.unlock(); + + dout(15) << "snap_name=" << m_snap_name << ", " + << "snap_id=" << m_prev_snap_id << dendl; + + std::shared_lock owner_locker{m_local_image_ctx->owner_lock}; + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_rename_snapshot>(this); + m_local_image_ctx->operations->execute_snap_rename( + m_prev_snap_id, m_snap_name.c_str(), ctx); +} + +template +void ApplyImageStateRequest::handle_rename_snapshot(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to protect snapshot " << m_snap_name << ": " + << cpp_strerror(r) << dendl; + finish(r); + return; + } + + rename_snapshot(); +} + +template +void ApplyImageStateRequest::set_snapshot_limit() { + dout(15) << dendl; + + // no need to even check the current limit -- just set it + std::shared_lock owner_locker{m_local_image_ctx->owner_lock}; + auto ctx = create_context_callback< + ApplyImageStateRequest, + &ApplyImageStateRequest::handle_set_snapshot_limit>(this); + m_local_image_ctx->operations->execute_snap_set_limit( + m_image_state.snap_limit, ctx); +} + +template +void ApplyImageStateRequest::handle_set_snapshot_limit(int r) { + dout(15) << "r=" << r << dendl; + + if (r < 0) { + derr << "failed to update snapshot limit: " << cpp_strerror(r) + << dendl; + } + + finish(r); +} + +template +void ApplyImageStateRequest::finish(int r) { + dout(15) << "r=" << r << dendl; + + m_on_finish->complete(r); + delete this; +} + +template +uint64_t ApplyImageStateRequest::compute_remote_snap_id( + uint64_t local_snap_id) { + ceph_assert(ceph_mutex_is_locked(m_local_image_ctx->image_lock)); + ceph_assert(ceph_mutex_is_locked(m_remote_image_ctx->image_lock)); + + // Search our local non-primary snapshots for a mapping to the remote + // snapshot. The non-primary mirror snapshot with the mappings will always + // come at or after the snapshot we are searching against + auto snap_it = m_local_image_ctx->snap_info.lower_bound(local_snap_id); + for (; snap_it != m_local_image_ctx->snap_info.end(); ++snap_it) { + auto mirror_ns = boost::get( + &snap_it->second.snap_namespace); + if (mirror_ns == nullptr || !mirror_ns->is_non_primary()) { + continue; + } + + if (mirror_ns->primary_mirror_uuid != m_remote_mirror_uuid) { + dout(20) << "local snapshot " << snap_it->first << " not tied to remote" + << dendl; + continue; + } else if (local_snap_id == snap_it->first) { + dout(15) << "local snapshot " << local_snap_id << " maps to " + << "remote snapshot " << mirror_ns->primary_snap_id << dendl; + return mirror_ns->primary_snap_id; + } + + const auto& snap_seqs = mirror_ns->snap_seqs; + for (auto [remote_snap_id_seq, local_snap_id_seq] : snap_seqs) { + if (local_snap_id_seq == local_snap_id) { + dout(15) << "local snapshot " << local_snap_id << " maps to " + << "remote snapshot " << remote_snap_id_seq << dendl; + return remote_snap_id_seq; + } + } + } + + // if we failed to find a match to a remote snapshot in our local non-primary + // snapshots, check the remote image for non-primary snapshot mappings back + // to our snapshot + for (snap_it = m_remote_image_ctx->snap_info.begin(); + snap_it != m_remote_image_ctx->snap_info.end(); ++snap_it) { + auto snap_id = snap_it->first; + auto mirror_ns = boost::get( + &snap_it->second.snap_namespace); + if (mirror_ns == nullptr || !mirror_ns->is_non_primary()) { + continue; + } + + if (mirror_ns->primary_mirror_uuid != m_local_mirror_uuid) { + dout(20) << "remote snapshot " << snap_id << " not tied to local" + << dendl; + continue; + } else if (mirror_ns->primary_snap_id == local_snap_id) { + dout(15) << "local snapshot " << local_snap_id << " maps to " + << "remote snapshot " << snap_id << dendl; + return snap_id; + } + + const auto& snap_seqs = mirror_ns->snap_seqs; + for (auto [local_snap_id_seq, remote_snap_id_seq] : snap_seqs) { + if (local_snap_id_seq == local_snap_id) { + dout(15) << "local snapshot " << local_snap_id << " maps to " + << "remote snapshot " << remote_snap_id_seq << dendl; + return remote_snap_id_seq; + } + } + } + + return CEPH_NOSNAP; +} + +template +void ApplyImageStateRequest::compute_local_to_remote_snap_ids() { + ceph_assert(ceph_mutex_is_locked(m_local_image_ctx->image_lock)); + std::shared_lock remote_image_locker{m_remote_image_ctx->image_lock}; + + for (const auto& [snap_id, snap_info] : m_local_image_ctx->snap_info) { + m_local_to_remote_snap_ids[snap_id] = compute_remote_snap_id(snap_id); + } + + dout(15) << "local_to_remote_snap_ids=" << m_local_to_remote_snap_ids + << dendl; +} + +} // namespace snapshot +} // namespace image_replayer +} // namespace mirror +} // namespace rbd + +template class rbd::mirror::image_replayer::snapshot::ApplyImageStateRequest; diff --git a/src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.h b/src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.h new file mode 100644 index 00000000000..0e2d09ddf07 --- /dev/null +++ b/src/tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.h @@ -0,0 +1,155 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef RBD_MIRROR_IMAGE_REPLAYER_SNAPSHOT_APPLY_IMAGE_STATE_REQUEST_H +#define RBD_MIRROR_IMAGE_REPLAYER_SNAPSHOT_APPLY_IMAGE_STATE_REQUEST_H + +#include "common/ceph_mutex.h" +#include "librbd/mirror/snapshot/Types.h" +#include +#include + +struct Context; + +namespace librbd { + +struct ImageCtx; + +} // namespace librbd + +namespace rbd { +namespace mirror { +namespace image_replayer { +namespace snapshot { + +template class EventPreprocessor; +template class ReplayStatusFormatter; +template class StateBuilder; + +template +class ApplyImageStateRequest { +public: + static ApplyImageStateRequest* create( + const std::string& local_mirror_uuid, + const std::string& remote_mirror_uuid, + ImageCtxT* local_image_ctx, + ImageCtxT* remote_image_ctx, + librbd::mirror::snapshot::ImageState image_state, + Context* on_finish) { + return new ApplyImageStateRequest(local_mirror_uuid, remote_mirror_uuid, + local_image_ctx, remote_image_ctx, + image_state, on_finish); + } + + ApplyImageStateRequest( + const std::string& local_mirror_uuid, + const std::string& remote_mirror_uuid, + ImageCtxT* local_image_ctx, + ImageCtxT* remote_image_ctx, + librbd::mirror::snapshot::ImageState image_state, + Context* on_finish); + + void send(); + +private: + /** + * @verbatim + * + * + * | + * v + * RENAME_IMAGE + * | + * | /---------\ + * | | | + * v v | + * UPDATE_FEATURES -----/ + * | + * v + * GET_IMAGE_META + * | + * | /---------\ + * | | | + * v v | + * UPDATE_IMAGE_META ---/ + * | + * | /---------\ + * | | | + * v v | + * UNPROTECT_SNAPSHOT | + * | | + * v | + * REMOVE_SNAPSHOT | + * | | + * v | + * PROTECT_SNAPSHOT | + * | | + * v | + * RENAME_SNAPSHOT -----/ + * | + * v + * SET_SNAPSHOT_LIMIT + * | + * v + * + * + * @endverbatim + */ + + std::string m_local_mirror_uuid; + std::string m_remote_mirror_uuid; + ImageCtxT* m_local_image_ctx; + ImageCtxT* m_remote_image_ctx; + librbd::mirror::snapshot::ImageState m_image_state; + Context* m_on_finish; + + std::map m_local_to_remote_snap_ids; + + uint64_t m_features = 0; + + std::map m_metadata; + + uint64_t m_prev_snap_id = 0; + std::string m_snap_name; + + void rename_image(); + void handle_rename_image(int r); + + void update_features(); + void handle_update_features(int r); + + void get_image_meta(); + void handle_get_image_meta(int r); + + void update_image_meta(); + void handle_update_image_meta(int r); + + void unprotect_snapshot(); + void handle_unprotect_snapshot(int r); + + void remove_snapshot(); + void handle_remove_snapshot(int r); + + void protect_snapshot(); + void handle_protect_snapshot(int r); + + void rename_snapshot(); + void handle_rename_snapshot(int r); + + void set_snapshot_limit(); + void handle_set_snapshot_limit(int r); + + void finish(int r); + + uint64_t compute_remote_snap_id(uint64_t snap_id); + void compute_local_to_remote_snap_ids(); +}; + +} // namespace snapshot +} // namespace image_replayer +} // namespace mirror +} // namespace rbd + +extern template class rbd::mirror::image_replayer::snapshot::ApplyImageStateRequest; + +#endif // RBD_MIRROR_IMAGE_REPLAYER_SNAPSHOT_APPLY_IMAGE_STATE_REQUEST_H -- 2.39.5