summaryrefslogtreecommitdiffstats
path: root/src/test/rbd_mirror/image_replayer
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/rbd_mirror/image_replayer')
-rw-r--r--src/test/rbd_mirror/image_replayer/journal/test_mock_CreateLocalImageRequest.cc341
-rw-r--r--src/test/rbd_mirror/image_replayer/journal/test_mock_EventPreprocessor.cc266
-rw-r--r--src/test/rbd_mirror/image_replayer/journal/test_mock_PrepareReplayRequest.cc751
-rw-r--r--src/test/rbd_mirror/image_replayer/journal/test_mock_Replayer.cc2162
-rw-r--r--src/test/rbd_mirror/image_replayer/snapshot/test_mock_ApplyImageStateRequest.cc641
-rw-r--r--src/test/rbd_mirror/image_replayer/snapshot/test_mock_CreateLocalImageRequest.cc356
-rw-r--r--src/test/rbd_mirror/image_replayer/snapshot/test_mock_Replayer.cc3327
-rw-r--r--src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc1248
-rw-r--r--src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc614
-rw-r--r--src/test/rbd_mirror/image_replayer/test_mock_GetMirrorImageIdRequest.cc107
-rw-r--r--src/test/rbd_mirror/image_replayer/test_mock_PrepareLocalImageRequest.cc505
-rw-r--r--src/test/rbd_mirror/image_replayer/test_mock_PrepareRemoteImageRequest.cc811
12 files changed, 11129 insertions, 0 deletions
diff --git a/src/test/rbd_mirror/image_replayer/journal/test_mock_CreateLocalImageRequest.cc b/src/test/rbd_mirror/image_replayer/journal/test_mock_CreateLocalImageRequest.cc
new file mode 100644
index 000000000..cc2267160
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/journal/test_mock_CreateLocalImageRequest.cc
@@ -0,0 +1,341 @@
+// -*- 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/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/CreateImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/journal/CreateLocalImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/journal/StateBuilder.h"
+#include "test/journal/mock/MockJournaler.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/rbd_mirror/mock/MockContextWQ.h"
+#include "test/rbd_mirror/mock/MockSafeTimer.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockTestImageCtx> {
+ typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+
+namespace util {
+
+static std::string s_image_id;
+
+template <>
+std::string generate_image_id<MockTestImageCtx>(librados::IoCtx&) {
+ ceph_assert(!s_image_id.empty());
+ return s_image_id;
+}
+
+} // namespace util
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+template <>
+struct Threads<librbd::MockTestImageCtx> {
+};
+
+namespace image_replayer {
+
+template<>
+struct CreateImageRequest<librbd::MockTestImageCtx> {
+ static CreateImageRequest* s_instance;
+ Context *on_finish = nullptr;
+
+ static CreateImageRequest* create(Threads<librbd::MockTestImageCtx>* threads,
+ librados::IoCtx &local_io_ctx,
+ const std::string &global_image_id,
+ const std::string &remote_mirror_uuid,
+ const std::string &local_image_name,
+ const std::string &local_image_id,
+ librbd::MockTestImageCtx *remote_image_ctx,
+ PoolMetaCache* pool_meta_cache,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ s_instance->construct(local_image_id);
+ return s_instance;
+ }
+
+ CreateImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~CreateImageRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD1(construct, void(const std::string&));
+ MOCK_METHOD0(send, void());
+};
+
+CreateImageRequest<librbd::MockTestImageCtx>*
+ CreateImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace journal {
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ std::string local_image_id;
+
+ std::string remote_mirror_uuid;
+ ::journal::MockJournalerProxy* remote_journaler = nullptr;
+ cls::journal::ClientState remote_client_state;
+ librbd::journal::MirrorPeerClientMeta remote_client_meta;
+};
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+#include "tools/rbd_mirror/image_replayer/journal/CreateLocalImageRequest.cc"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::WithArg;
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace journal {
+
+class TestMockImageReplayerJournalCreateLocalImageRequest : public TestMockFixture {
+public:
+ typedef CreateLocalImageRequest<librbd::MockTestImageCtx> MockCreateLocalImageRequest;
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef CreateImageRequest<librbd::MockTestImageCtx> MockCreateImageRequest;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::RBD rbd;
+ 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_remote_image_ctx = new librbd::MockTestImageCtx(*m_remote_image_ctx);
+ }
+
+ void TearDown() override {
+ delete m_mock_remote_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_journaler_register_client(
+ ::journal::MockJournaler& mock_journaler,
+ const librbd::journal::ClientData& client_data, int r) {
+ bufferlist bl;
+ encode(client_data, bl);
+
+ EXPECT_CALL(mock_journaler, register_client(ContentsEqual(bl), _))
+ .WillOnce(WithArg<1>(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ })));
+ }
+
+ void expect_journaler_unregister_client(
+ ::journal::MockJournaler& mock_journaler, int r) {
+ EXPECT_CALL(mock_journaler, unregister_client(_))
+ .WillOnce(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ }));
+ }
+
+ void expect_journaler_update_client(
+ ::journal::MockJournaler& mock_journaler,
+ const librbd::journal::ClientData& client_data, int r) {
+ bufferlist bl;
+ encode(client_data, bl);
+
+ EXPECT_CALL(mock_journaler, update_client(ContentsEqual(bl), _))
+ .WillOnce(WithArg<1>(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ })));
+ }
+
+ void expect_create_image(MockCreateImageRequest& mock_create_image_request,
+ const std::string& image_id, int r) {
+ EXPECT_CALL(mock_create_image_request, construct(image_id));
+ EXPECT_CALL(mock_create_image_request, send())
+ .WillOnce(Invoke([this, &mock_create_image_request, r]() {
+ m_threads->work_queue->queue(mock_create_image_request.on_finish, r);
+ }));
+ }
+
+ MockCreateLocalImageRequest* create_request(
+ MockThreads& mock_threads,
+ MockStateBuilder& mock_state_builder,
+ const std::string& global_image_id,
+ Context* on_finish) {
+ return new MockCreateLocalImageRequest(
+ &mock_threads, m_local_io_ctx, m_mock_remote_image_ctx,
+ global_image_id, nullptr, nullptr, &mock_state_builder,
+ on_finish);
+ }
+
+ librbd::ImageCtx *m_remote_image_ctx;
+ librbd::MockTestImageCtx *m_mock_remote_image_ctx = nullptr;
+};
+
+TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, Success) {
+ InSequence seq;
+
+ // re-register the client
+ ::journal::MockJournaler mock_journaler;
+ expect_journaler_unregister_client(mock_journaler, 0);
+
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ librbd::util::s_image_id = "local image id";
+ mirror_peer_client_meta.image_id = "local image id";
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
+ librbd::journal::ClientData client_data;
+ client_data.client_meta = mirror_peer_client_meta;
+ expect_journaler_register_client(mock_journaler, client_data, 0);
+
+ // create the missing local image
+ MockCreateImageRequest mock_create_image_request;
+ expect_create_image(mock_create_image_request, "local image id", 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads;
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ ASSERT_EQ("local image id", mock_state_builder.local_image_id);
+ ASSERT_EQ("local image id", mock_state_builder.remote_client_meta.image_id);
+ ASSERT_EQ(librbd::journal::MIRROR_PEER_STATE_SYNCING,
+ mock_state_builder.remote_client_meta.state);
+}
+
+TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, UnregisterError) {
+ InSequence seq;
+
+ // re-register the client
+ ::journal::MockJournaler mock_journaler;
+ expect_journaler_unregister_client(mock_journaler, -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads;
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, RegisterError) {
+ InSequence seq;
+
+ // re-register the client
+ ::journal::MockJournaler mock_journaler;
+ expect_journaler_unregister_client(mock_journaler, 0);
+
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ librbd::util::s_image_id = "local image id";
+ mirror_peer_client_meta.image_id = "local image id";
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
+ librbd::journal::ClientData client_data;
+ client_data.client_meta = mirror_peer_client_meta;
+ expect_journaler_register_client(mock_journaler, client_data, -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads;
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, CreateImageError) {
+ InSequence seq;
+
+ // re-register the client
+ ::journal::MockJournaler mock_journaler;
+ expect_journaler_unregister_client(mock_journaler, 0);
+
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ librbd::util::s_image_id = "local image id";
+ mirror_peer_client_meta.image_id = "local image id";
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
+ librbd::journal::ClientData client_data;
+ client_data.client_meta = mirror_peer_client_meta;
+ expect_journaler_register_client(mock_journaler, client_data, 0);
+
+ // create the missing local image
+ MockCreateImageRequest mock_create_image_request;
+ expect_create_image(mock_create_image_request, "local image id", -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads;
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalCreateLocalImageRequest, CreateImageDuplicate) {
+ InSequence seq;
+
+ // re-register the client
+ ::journal::MockJournaler mock_journaler;
+ expect_journaler_unregister_client(mock_journaler, 0);
+
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ librbd::util::s_image_id = "local image id";
+ mirror_peer_client_meta.image_id = "local image id";
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
+ librbd::journal::ClientData client_data;
+ client_data.client_meta = mirror_peer_client_meta;
+ expect_journaler_register_client(mock_journaler, client_data, 0);
+
+ // create the missing local image
+ MockCreateImageRequest mock_create_image_request;
+ expect_create_image(mock_create_image_request, "local image id", -EBADF);
+
+ // re-register the client
+ expect_journaler_unregister_client(mock_journaler, 0);
+ expect_journaler_register_client(mock_journaler, client_data, 0);
+
+ // re-create the local image
+ expect_create_image(mock_create_image_request, "local image id", 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads;
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/journal/test_mock_EventPreprocessor.cc b/src/test/rbd_mirror/image_replayer/journal/test_mock_EventPreprocessor.cc
new file mode 100644
index 000000000..ad0055281
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/journal/test_mock_EventPreprocessor.cc
@@ -0,0 +1,266 @@
+// -*- 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/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/journal/EventPreprocessor.h"
+#include "test/journal/mock/MockJournaler.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 journal {
+
+template <>
+struct TypeTraits<librbd::MockTestImageCtx> {
+ typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace librbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/journal/EventPreprocessor.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace journal {
+
+using testing::_;
+using testing::WithArg;
+
+class TestMockImageReplayerJournalEventPreprocessor : public TestMockFixture {
+public:
+ typedef EventPreprocessor<librbd::MockTestImageCtx> MockEventPreprocessor;
+
+ 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_image_refresh(librbd::MockTestImageCtx &mock_remote_image_ctx, int r) {
+ EXPECT_CALL(*mock_remote_image_ctx.state, refresh(_))
+ .WillOnce(CompleteContext(r));
+ }
+
+ void expect_update_client(::journal::MockJournaler &mock_journaler, int r) {
+ EXPECT_CALL(mock_journaler, update_client(_, _))
+ .WillOnce(WithArg<1>(CompleteContext(r)));
+ }
+
+ librbd::ImageCtx *m_local_image_ctx;
+ librbd::journal::MirrorPeerClientMeta m_client_meta;
+
+};
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, IsNotRequired) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{librbd::journal::RenameEvent{}};
+ ASSERT_FALSE(event_preprocessor.is_required(event_entry));
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, IsRequiredSnapMapPrune) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ m_client_meta.snap_seqs = {{1, 2}, {3, 4}};
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{librbd::journal::RenameEvent{}};
+ ASSERT_TRUE(event_preprocessor.is_required(event_entry));
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, IsRequiredSnapRename) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{librbd::journal::SnapRenameEvent{}};
+ ASSERT_TRUE(event_preprocessor.is_required(event_entry));
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, PreprocessSnapMapPrune) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ expect_image_refresh(mock_local_image_ctx, 0);
+ expect_update_client(mock_remote_journaler, 0);
+
+ mock_local_image_ctx.snap_info = {
+ {6, librbd::SnapInfo{"snap", cls::rbd::UserSnapshotNamespace(), 0U, {}, 0U, 0U, utime_t()}}};
+ m_client_meta.snap_seqs = {{1, 2}, {3, 4}, {5, 6}};
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{librbd::journal::RenameEvent{}};
+ C_SaferCond ctx;
+ event_preprocessor.preprocess(&event_entry, &ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ librbd::SnapSeqs expected_snap_seqs = {{5, 6}};
+ ASSERT_EQ(expected_snap_seqs, m_client_meta.snap_seqs);
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, PreprocessSnapRename) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ expect_image_refresh(mock_local_image_ctx, 0);
+ expect_update_client(mock_remote_journaler, 0);
+
+ mock_local_image_ctx.snap_ids = {{{cls::rbd::UserSnapshotNamespace(), "snap"}, 6}};
+ mock_local_image_ctx.snap_info = {
+ {6, librbd::SnapInfo{"snap", cls::rbd::UserSnapshotNamespace(), 0U, {}, 0U, 0U, utime_t()}}};
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{
+ librbd::journal::SnapRenameEvent{0, 5, "snap", "new_snap"}};
+ C_SaferCond ctx;
+ event_preprocessor.preprocess(&event_entry, &ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ librbd::SnapSeqs expected_snap_seqs = {{5, 6}};
+ ASSERT_EQ(expected_snap_seqs, m_client_meta.snap_seqs);
+
+ librbd::journal::SnapRenameEvent *event =
+ boost::get<librbd::journal::SnapRenameEvent>(&event_entry.event);
+ ASSERT_EQ(6U, event->snap_id);
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, PreprocessSnapRenameMissing) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ expect_image_refresh(mock_local_image_ctx, 0);
+
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{
+ librbd::journal::SnapRenameEvent{0, 5, "snap", "new_snap"}};
+ C_SaferCond ctx;
+ event_preprocessor.preprocess(&event_entry, &ctx);
+ ASSERT_EQ(-ENOENT, ctx.wait());
+
+ librbd::journal::SnapRenameEvent *event =
+ boost::get<librbd::journal::SnapRenameEvent>(&event_entry.event);
+ ASSERT_EQ(CEPH_NOSNAP, event->snap_id);
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, PreprocessSnapRenameKnown) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ expect_image_refresh(mock_local_image_ctx, 0);
+
+ mock_local_image_ctx.snap_info = {
+ {6, librbd::SnapInfo{"snap", cls::rbd::UserSnapshotNamespace(), 0U, {}, 0U, 0U, utime_t()}}};
+ m_client_meta.snap_seqs = {{5, 6}};
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{
+ librbd::journal::SnapRenameEvent{0, 5, "snap", "new_snap"}};
+ C_SaferCond ctx;
+ event_preprocessor.preprocess(&event_entry, &ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ librbd::SnapSeqs expected_snap_seqs = {{5, 6}};
+ ASSERT_EQ(expected_snap_seqs, m_client_meta.snap_seqs);
+
+ librbd::journal::SnapRenameEvent *event =
+ boost::get<librbd::journal::SnapRenameEvent>(&event_entry.event);
+ ASSERT_EQ(6U, event->snap_id);
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, PreprocessRefreshError) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ expect_image_refresh(mock_local_image_ctx, -EINVAL);
+
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{librbd::journal::RenameEvent{}};
+ C_SaferCond ctx;
+ event_preprocessor.preprocess(&event_entry, &ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalEventPreprocessor, PreprocessClientUpdateError) {
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ ::journal::MockJournaler mock_remote_journaler;
+
+ expect_image_refresh(mock_local_image_ctx, 0);
+ expect_update_client(mock_remote_journaler, -EINVAL);
+
+ mock_local_image_ctx.snap_ids = {{{cls::rbd::UserSnapshotNamespace(), "snap"}, 6}};
+ mock_local_image_ctx.snap_info = {
+ {6, librbd::SnapInfo{"snap", cls::rbd::UserSnapshotNamespace(), 0U, {}, 0U, 0U, utime_t()}}};
+ MockEventPreprocessor event_preprocessor(mock_local_image_ctx,
+ mock_remote_journaler,
+ "local mirror uuid",
+ &m_client_meta,
+ m_threads->work_queue);
+
+ librbd::journal::EventEntry event_entry{
+ librbd::journal::SnapRenameEvent{0, 5, "snap", "new_snap"}};
+ C_SaferCond ctx;
+ event_preprocessor.preprocess(&event_entry, &ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/journal/test_mock_PrepareReplayRequest.cc b/src/test/rbd_mirror/image_replayer/journal/test_mock_PrepareReplayRequest.cc
new file mode 100644
index 000000000..4aa951629
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/journal/test_mock_PrepareReplayRequest.cc
@@ -0,0 +1,751 @@
+// -*- 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/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/journal/PrepareReplayRequest.h"
+#include "tools/rbd_mirror/image_replayer/journal/StateBuilder.h"
+#include "test/journal/mock/MockJournaler.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockJournal.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockTestImageCtx> {
+ typedef ::journal::MockJournaler Journaler;
+};
+
+} // namespace journal
+} // namespace rbd
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace journal {
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ StateBuilder(librbd::MockTestImageCtx& local_image_ctx,
+ ::journal::MockJournaler& remote_journaler,
+ const librbd::journal::MirrorPeerClientMeta& remote_client_meta)
+ : local_image_ctx(&local_image_ctx),
+ local_image_id(local_image_ctx.id),
+ remote_journaler(&remote_journaler),
+ remote_client_meta(remote_client_meta) {
+ }
+
+ librbd::MockTestImageCtx* local_image_ctx;
+ std::string local_image_id;
+
+ std::string remote_mirror_uuid = "remote mirror uuid";
+ ::journal::MockJournaler* remote_journaler = nullptr;
+ librbd::journal::MirrorPeerClientMeta remote_client_meta;
+};
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/journal/PrepareReplayRequest.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace journal {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockImageReplayerJournalPrepareReplayRequest : public TestMockFixture {
+public:
+ typedef PrepareReplayRequest<librbd::MockTestImageCtx> MockPrepareReplayRequest;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+ typedef std::list<cls::journal::Tag> Tags;
+
+ 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_journaler_get_client(::journal::MockJournaler &mock_journaler,
+ const std::string &client_id,
+ cls::journal::Client &client, int r) {
+ EXPECT_CALL(mock_journaler, get_client(StrEq(client_id), _, _))
+ .WillOnce(DoAll(WithArg<1>(Invoke([client](cls::journal::Client *out_client) {
+ *out_client = client;
+ })),
+ WithArg<2>(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ }))));
+ }
+
+ void expect_journaler_update_client(::journal::MockJournaler &mock_journaler,
+ const librbd::journal::ClientData &client_data,
+ int r) {
+ bufferlist bl;
+ encode(client_data, bl);
+
+ EXPECT_CALL(mock_journaler, update_client(ContentsEqual(bl), _))
+ .WillOnce(WithArg<1>(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ })));
+ }
+
+ void expect_journaler_get_tags(::journal::MockJournaler &mock_journaler,
+ uint64_t tag_class, const Tags& tags,
+ int r) {
+ EXPECT_CALL(mock_journaler, get_tags(tag_class, _, _))
+ .WillOnce(DoAll(WithArg<1>(Invoke([tags](Tags *out_tags) {
+ *out_tags = tags;
+ })),
+ WithArg<2>(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ }))));
+ }
+
+ void expect_journal_get_tag_tid(librbd::MockJournal &mock_journal,
+ uint64_t tag_tid) {
+ EXPECT_CALL(mock_journal, get_tag_tid()).WillOnce(Return(tag_tid));
+ }
+
+ void expect_journal_get_tag_data(librbd::MockJournal &mock_journal,
+ const librbd::journal::TagData &tag_data) {
+ EXPECT_CALL(mock_journal, get_tag_data()).WillOnce(Return(tag_data));
+ }
+
+ void expect_is_resync_requested(librbd::MockJournal &mock_journal,
+ bool do_resync, int r) {
+ EXPECT_CALL(mock_journal, is_resync_requested(_))
+ .WillOnce(DoAll(SetArgPointee<0>(do_resync),
+ Return(r)));
+ }
+
+ bufferlist encode_tag_data(const librbd::journal::TagData &tag_data) {
+ bufferlist bl;
+ encode(tag_data, bl);
+ return bl;
+ }
+
+ MockPrepareReplayRequest* create_request(
+ MockStateBuilder& mock_state_builder,
+ const std::string& local_mirror_uuid,
+ bool* resync_requested, bool* syncing, Context* on_finish) {
+ return new MockPrepareReplayRequest(
+ local_mirror_uuid, nullptr, &mock_state_builder, resync_requested,
+ syncing, on_finish);
+ }
+
+ librbd::ImageCtx *m_local_image_ctx = nullptr;
+};
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, Success) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {"remote mirror uuid"});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // single promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 344, 99})},
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, 0);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_FALSE(resync_requested);
+ ASSERT_FALSE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, NoLocalJournal) {
+ InSequence seq;
+
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ C_SaferCond ctx;
+ ::journal::MockJournaler mock_remote_journaler;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, ResyncRequested) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, true, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {"remote mirror uuid"});
+
+ C_SaferCond ctx;
+ ::journal::MockJournaler mock_remote_journaler;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_TRUE(resync_requested);
+ ASSERT_FALSE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, ResyncRequestedError) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, -EINVAL);
+
+ C_SaferCond ctx;
+ ::journal::MockJournaler mock_remote_journaler;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, Syncing) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {"remote mirror uuid"});
+
+ C_SaferCond ctx;
+ ::journal::MockJournaler mock_remote_journaler;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_FALSE(resync_requested);
+ ASSERT_TRUE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, GetRemoteTagClassError) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {"remote mirror uuid"});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, -EINVAL);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, GetRemoteTagsError) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {"remote mirror uuid"});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // single promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 344, 99})},
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, -EINVAL);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, LocalDemotedRemoteSyncingState) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ "remote mirror uuid", true, 4, 1});
+
+ // update client state
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{
+ mock_local_image_ctx.id};
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ librbd::journal::ClientData client_data;
+ client_data.client_meta = mirror_peer_client_meta;
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_update_client(mock_remote_journaler, client_data, 0);
+
+ // lookup remote image tag class
+ client_data = {librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // remote demotion / promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 1, 99})},
+ {3, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 2, 1})},
+ {4, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 3, 1})},
+ {5, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 4, 1})},
+ {6, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 5, 1})},
+ {7, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 6, 1})},
+ {8, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 7, 1})}
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, 0);
+
+ C_SaferCond ctx;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_FALSE(resync_requested);
+ ASSERT_FALSE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, UpdateClientError) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {"remote mirror uuid"});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // single promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 344, 99})},
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, 0);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_FALSE(resync_requested);
+ ASSERT_FALSE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, RemoteDemotePromote) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {"remote mirror uuid"});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // remote demotion / promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 1, 99})},
+ {3, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 2, 1})},
+ {4, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 2, 1})},
+ {5, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 4, 369})}
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, 0);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_FALSE(resync_requested);
+ ASSERT_FALSE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, MultipleRemoteDemotePromotes) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ "remote mirror uuid", true, 4, 1});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // remote demotion / promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 1, 99})},
+ {3, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 2, 1})},
+ {4, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 3, 1})},
+ {5, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 4, 1})},
+ {6, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 5, 1})},
+ {7, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 6, 1})},
+ {8, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 7, 1})}
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, 0);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_FALSE(resync_requested);
+ ASSERT_FALSE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, LocalDemoteRemotePromote) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 346);
+ expect_journal_get_tag_data(mock_journal,
+ {librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 345, 1});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // remote demotion / promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({"local mirror uuid", "local mirror uuid",
+ true, 344, 99})},
+ {3, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ "local mirror uuid", true, 345, 1})},
+ {4, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 3, 1})}
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, 0);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_FALSE(resync_requested);
+ ASSERT_FALSE(syncing);
+}
+
+TEST_F(TestMockImageReplayerJournalPrepareReplayRequest, SplitBrainForcePromote) {
+ InSequence seq;
+
+ librbd::MockJournal mock_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ mock_local_image_ctx.journal = &mock_journal;
+
+ // check initial state
+ expect_is_resync_requested(mock_journal, false, 0);
+ expect_journal_get_tag_tid(mock_journal, 345);
+ expect_journal_get_tag_data(mock_journal, {librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ true, 344, 0});
+
+ // lookup remote image tag class
+ librbd::journal::ClientData client_data{
+ librbd::journal::ImageClientMeta{123}};
+ cls::journal::Client client;
+ encode(client_data, client.data);
+ ::journal::MockJournaler mock_remote_journaler;
+ expect_journaler_get_client(mock_remote_journaler,
+ librbd::Journal<>::IMAGE_CLIENT_ID,
+ client, 0);
+
+ // remote demotion / promotion event
+ Tags tags = {
+ {2, 123, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 1, 99})},
+ {3, 123, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 2, 1})}
+ };
+ expect_journaler_get_tags(mock_remote_journaler, 123, tags, 0);
+
+ C_SaferCond ctx;
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ mirror_peer_client_meta.image_id = mock_local_image_ctx.id;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ mirror_peer_client_meta);
+ bool resync_requested;
+ bool syncing;
+ auto request = create_request(mock_state_builder, "local mirror uuid",
+ &resync_requested, &syncing, &ctx);
+ request->send();
+ ASSERT_EQ(-EEXIST, ctx.wait());
+}
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/journal/test_mock_Replayer.cc b/src/test/rbd_mirror/image_replayer/journal/test_mock_Replayer.cc
new file mode 100644
index 000000000..7c8defb3d
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/journal/test_mock_Replayer.cc
@@ -0,0 +1,2162 @@
+// -*- 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/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/CloseImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/ReplayerListener.h"
+#include "tools/rbd_mirror/image_replayer/Utils.h"
+#include "tools/rbd_mirror/image_replayer/journal/Replayer.h"
+#include "tools/rbd_mirror/image_replayer/journal/EventPreprocessor.h"
+#include "tools/rbd_mirror/image_replayer/journal/ReplayStatusFormatter.h"
+#include "tools/rbd_mirror/image_replayer/journal/StateBuilder.h"
+#include "test/journal/mock/MockJournaler.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/rbd_mirror/mock/MockContextWQ.h"
+#include "test/rbd_mirror/mock/MockSafeTimer.h"
+#include <boost/intrusive_ptr.hpp>
+
+using namespace std::chrono_literals;
+
+namespace librbd {
+
+namespace {
+
+struct MockTestJournal;
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx &image_ctx,
+ MockTestJournal& mock_test_journal)
+ : librbd::MockImageCtx(image_ctx), journal(&mock_test_journal) {
+ }
+
+ MockTestJournal* journal = nullptr;
+};
+
+struct MockTestJournal : public MockJournal {
+ MOCK_METHOD2(start_external_replay, void(journal::Replay<MockTestImageCtx> **,
+ Context *on_start));
+ MOCK_METHOD0(stop_external_replay, void());
+};
+
+} // anonymous namespace
+
+namespace journal {
+
+template <>
+struct TypeTraits<librbd::MockTestImageCtx> {
+ typedef ::journal::MockJournaler Journaler;
+ typedef ::journal::MockReplayEntryProxy ReplayEntry;
+};
+
+template<>
+struct Replay<MockTestImageCtx> {
+ MOCK_METHOD2(decode, int(bufferlist::const_iterator *, EventEntry *));
+ MOCK_METHOD3(process, void(const EventEntry &, Context *, Context *));
+ MOCK_METHOD1(flush, void(Context*));
+ MOCK_METHOD2(shut_down, void(bool, Context*));
+};
+
+} // namespace journal
+} // namespace librbd
+
+namespace boost {
+
+template<>
+struct intrusive_ptr<librbd::MockTestJournal> {
+ intrusive_ptr() {
+ }
+ intrusive_ptr(librbd::MockTestJournal* mock_test_journal)
+ : mock_test_journal(mock_test_journal) {
+ }
+
+ librbd::MockTestJournal* operator->() {
+ return mock_test_journal;
+ }
+
+ void reset() {
+ mock_test_journal = nullptr;
+ }
+
+ const librbd::MockTestJournal* get() const {
+ return mock_test_journal;
+ }
+
+ template<typename T>
+ bool operator==(T* t) const {
+ return (mock_test_journal == t);
+ }
+
+ librbd::MockTestJournal* mock_test_journal = nullptr;
+};
+
+} // namespace boost
+
+namespace rbd {
+namespace mirror {
+
+template <>
+struct Threads<librbd::MockTestImageCtx> {
+ MockSafeTimer *timer;
+ ceph::mutex &timer_lock;
+
+ MockContextWQ *work_queue;
+
+ Threads(Threads<librbd::ImageCtx>* threads)
+ : timer(new MockSafeTimer()),
+ timer_lock(threads->timer_lock),
+ work_queue(new MockContextWQ()) {
+ }
+ ~Threads() {
+ delete timer;
+ delete work_queue;
+ }
+};
+
+namespace {
+
+struct MockReplayerListener : public image_replayer::ReplayerListener {
+ MOCK_METHOD0(handle_notification, void());
+};
+
+} // anonymous namespace
+
+namespace image_replayer {
+
+template<>
+struct CloseImageRequest<librbd::MockTestImageCtx> {
+ static CloseImageRequest* s_instance;
+ librbd::MockTestImageCtx **image_ctx = nullptr;
+ Context *on_finish = nullptr;
+
+ static CloseImageRequest* create(librbd::MockTestImageCtx **image_ctx,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->image_ctx = image_ctx;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ CloseImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+
+ ~CloseImageRequest() {
+ ceph_assert(s_instance == this);
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+CloseImageRequest<librbd::MockTestImageCtx>* CloseImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace journal {
+
+template <>
+struct EventPreprocessor<librbd::MockTestImageCtx> {
+ static EventPreprocessor *s_instance;
+
+ static EventPreprocessor *create(librbd::MockTestImageCtx &local_image_ctx,
+ ::journal::MockJournaler &remote_journaler,
+ const std::string &local_mirror_uuid,
+ librbd::journal::MirrorPeerClientMeta *client_meta,
+ MockContextWQ *work_queue) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ static void destroy(EventPreprocessor* processor) {
+ }
+
+ EventPreprocessor() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+
+ ~EventPreprocessor() {
+ ceph_assert(s_instance == this);
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD1(is_required, bool(const librbd::journal::EventEntry &));
+ MOCK_METHOD2(preprocess, void(librbd::journal::EventEntry *, Context *));
+};
+
+template<>
+struct ReplayStatusFormatter<librbd::MockTestImageCtx> {
+ static ReplayStatusFormatter* s_instance;
+
+ static ReplayStatusFormatter* create(::journal::MockJournaler *journaler,
+ const std::string &mirror_uuid) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ static void destroy(ReplayStatusFormatter* formatter) {
+ }
+
+ ReplayStatusFormatter() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+
+ ~ReplayStatusFormatter() {
+ ceph_assert(s_instance == this);
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD1(handle_entry_processed, void(uint64_t));
+ MOCK_METHOD2(get_or_send_update, bool(std::string *description, Context *on_finish));
+};
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ StateBuilder(librbd::MockTestImageCtx& local_image_ctx,
+ ::journal::MockJournaler& remote_journaler,
+ const librbd::journal::MirrorPeerClientMeta& remote_client_meta)
+ : local_image_ctx(&local_image_ctx),
+ remote_journaler(&remote_journaler),
+ remote_client_meta(remote_client_meta) {
+ }
+
+ librbd::MockTestImageCtx* local_image_ctx;
+ std::string remote_mirror_uuid = "remote mirror uuid";
+ ::journal::MockJournaler* remote_journaler = nullptr;
+ librbd::journal::MirrorPeerClientMeta remote_client_meta;
+};
+
+EventPreprocessor<librbd::MockTestImageCtx>* EventPreprocessor<librbd::MockTestImageCtx>::s_instance = nullptr;
+ReplayStatusFormatter<librbd::MockTestImageCtx>* ReplayStatusFormatter<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+#include "tools/rbd_mirror/image_replayer/journal/Replayer.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace journal {
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::MatcherCast;
+using ::testing::Return;
+using ::testing::ReturnArg;
+using ::testing::SaveArg;
+using ::testing::SetArgPointee;
+using ::testing::WithArg;
+
+class TestMockImageReplayerJournalReplayer : public TestMockFixture {
+public:
+ typedef Replayer<librbd::MockTestImageCtx> MockReplayer;
+ typedef EventPreprocessor<librbd::MockTestImageCtx> MockEventPreprocessor;
+ typedef ReplayStatusFormatter<librbd::MockTestImageCtx> MockReplayStatusFormatter;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef CloseImageRequest<librbd::MockTestImageCtx> MockCloseImageRequest;
+ typedef librbd::journal::Replay<librbd::MockTestImageCtx> MockReplay;
+
+ 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));
+ }
+
+ bufferlist encode_tag_data(const librbd::journal::TagData &tag_data) {
+ bufferlist bl;
+ encode(tag_data, bl);
+ return bl;
+ }
+
+ void expect_work_queue_repeatedly(MockThreads &mock_threads) {
+ EXPECT_CALL(*mock_threads.work_queue, queue(_, _))
+ .WillRepeatedly(Invoke([this](Context *ctx, int r) {
+ m_threads->work_queue->queue(ctx, r);
+ }));
+ }
+
+ void expect_add_event_after_repeatedly(MockThreads &mock_threads) {
+ EXPECT_CALL(*mock_threads.timer, add_event_after(_, _))
+ .WillRepeatedly(
+ DoAll(Invoke([this](double seconds, Context *ctx) {
+ m_threads->timer->add_event_after(seconds, ctx);
+ }),
+ ReturnArg<1>()));
+ EXPECT_CALL(*mock_threads.timer, cancel_event(_))
+ .WillRepeatedly(
+ Invoke([this](Context *ctx) {
+ return m_threads->timer->cancel_event(ctx);
+ }));
+ }
+
+ void expect_init(::journal::MockJournaler &mock_journaler, int r) {
+ EXPECT_CALL(mock_journaler, init(_))
+ .WillOnce(CompleteContext(m_threads->work_queue, r));
+ }
+
+ void expect_stop_replay(::journal::MockJournaler &mock_journaler, int r) {
+ EXPECT_CALL(mock_journaler, stop_replay(_))
+ .WillOnce(CompleteContext(r));
+ }
+
+ void expect_shut_down(MockReplay &mock_replay, bool cancel_ops, int r) {
+ EXPECT_CALL(mock_replay, shut_down(cancel_ops, _))
+ .WillOnce(WithArg<1>(CompleteContext(m_threads->work_queue, r)));
+ }
+
+ void expect_get_cached_client(::journal::MockJournaler &mock_journaler,
+ const std::string& client_id,
+ const cls::journal::Client& client,
+ const librbd::journal::ClientMeta& client_meta,
+ int r) {
+ librbd::journal::ClientData client_data;
+ client_data.client_meta = client_meta;
+
+ cls::journal::Client client_copy{client};
+ encode(client_data, client_copy.data);
+
+ EXPECT_CALL(mock_journaler, get_cached_client(client_id, _))
+ .WillOnce(DoAll(SetArgPointee<1>(client_copy),
+ Return(r)));
+ }
+
+ void expect_start_external_replay(librbd::MockTestJournal &mock_journal,
+ MockReplay *mock_replay, int r) {
+ EXPECT_CALL(mock_journal, start_external_replay(_, _))
+ .WillOnce(DoAll(SetArgPointee<0>(mock_replay),
+ WithArg<1>(CompleteContext(m_threads->work_queue, r))));
+ }
+
+ void expect_is_tag_owner(librbd::MockTestJournal &mock_journal,
+ bool is_owner) {
+ EXPECT_CALL(mock_journal, is_tag_owner()).WillOnce(Return(is_owner));
+ }
+
+ void expect_is_resync_requested(librbd::MockTestJournal &mock_journal,
+ int r, bool resync_requested) {
+ EXPECT_CALL(mock_journal, is_resync_requested(_)).WillOnce(
+ DoAll(SetArgPointee<0>(resync_requested),
+ Return(r)));
+ }
+
+ void expect_get_commit_tid_in_debug(
+ ::journal::MockReplayEntry &mock_replay_entry) {
+ // It is used in debug messages and depends on debug level
+ EXPECT_CALL(mock_replay_entry, get_commit_tid())
+ .Times(AtLeast(0))
+ .WillRepeatedly(Return(0));
+ }
+
+ void expect_get_tag_tid_in_debug(librbd::MockTestJournal &mock_journal) {
+ // It is used in debug messages and depends on debug level
+ EXPECT_CALL(mock_journal, get_tag_tid()).Times(AtLeast(0))
+ .WillRepeatedly(Return(0));
+ }
+
+ void expect_committed(::journal::MockReplayEntry &mock_replay_entry,
+ ::journal::MockJournaler &mock_journaler, int times) {
+ EXPECT_CALL(mock_replay_entry, get_data()).Times(times);
+ EXPECT_CALL(mock_journaler, committed(
+ MatcherCast<const ::journal::MockReplayEntryProxy&>(_)))
+ .Times(times);
+ }
+
+ void expect_try_pop_front(::journal::MockJournaler &mock_journaler,
+ uint64_t replay_tag_tid, bool entries_available) {
+ EXPECT_CALL(mock_journaler, try_pop_front(_, _))
+ .WillOnce(DoAll(SetArgPointee<0>(::journal::MockReplayEntryProxy()),
+ SetArgPointee<1>(replay_tag_tid),
+ Return(entries_available)));
+ }
+
+ void expect_try_pop_front_return_no_entries(
+ ::journal::MockJournaler &mock_journaler, Context *on_finish) {
+ EXPECT_CALL(mock_journaler, try_pop_front(_, _))
+ .WillOnce(DoAll(Invoke([on_finish](::journal::MockReplayEntryProxy *e,
+ uint64_t *t) {
+ on_finish->complete(0);
+ }),
+ Return(false)));
+ }
+
+ void expect_get_tag(::journal::MockJournaler &mock_journaler,
+ const cls::journal::Tag &tag, int r) {
+ EXPECT_CALL(mock_journaler, get_tag(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(tag),
+ WithArg<2>(CompleteContext(r))));
+ }
+
+ void expect_allocate_tag(librbd::MockTestJournal &mock_journal, int r) {
+ EXPECT_CALL(mock_journal, allocate_tag(_, _, _))
+ .WillOnce(WithArg<2>(CompleteContext(r)));
+ }
+
+ void expect_preprocess(MockEventPreprocessor &mock_event_preprocessor,
+ bool required, int r) {
+ EXPECT_CALL(mock_event_preprocessor, is_required(_))
+ .WillOnce(Return(required));
+ if (required) {
+ EXPECT_CALL(mock_event_preprocessor, preprocess(_, _))
+ .WillOnce(WithArg<1>(CompleteContext(r)));
+ }
+ }
+
+ void expect_process(MockReplay &mock_replay,
+ int on_ready_r, int on_commit_r) {
+ EXPECT_CALL(mock_replay, process(_, _, _))
+ .WillOnce(DoAll(WithArg<1>(CompleteContext(on_ready_r)),
+ WithArg<2>(CompleteContext(on_commit_r))));
+ }
+
+ void expect_flush(MockReplay& mock_replay, int r) {
+ EXPECT_CALL(mock_replay, flush(_))
+ .WillOnce(CompleteContext(m_threads->work_queue, r));
+ }
+
+ void expect_flush_commit_position(::journal::MockJournaler& mock_journal,
+ int r) {
+ EXPECT_CALL(mock_journal, flush_commit_position(_))
+ .WillOnce(CompleteContext(m_threads->work_queue, r));
+ }
+
+ void expect_get_tag_data(librbd::MockTestJournal& mock_local_journal,
+ const librbd::journal::TagData& tag_data) {
+ EXPECT_CALL(mock_local_journal, get_tag_data())
+ .WillOnce(Return(tag_data));
+ }
+
+ void expect_send(MockCloseImageRequest &mock_close_image_request, int r) {
+ EXPECT_CALL(mock_close_image_request, send())
+ .WillOnce(Invoke([this, &mock_close_image_request, r]() {
+ *mock_close_image_request.image_ctx = nullptr;
+ m_threads->work_queue->queue(mock_close_image_request.on_finish, r);
+ }));
+ }
+
+ void expect_notification(MockThreads& mock_threads,
+ MockReplayerListener& mock_replayer_listener) {
+ EXPECT_CALL(mock_replayer_listener, handle_notification())
+ .WillOnce(Invoke([this]() {
+ std::unique_lock locker{m_lock};
+ m_notified = true;
+ m_cond.notify_all();
+ }));
+ }
+
+ int wait_for_notification() {
+ std::unique_lock locker{m_lock};
+ while (!m_notified) {
+ if (m_cond.wait_for(locker, 10s) == std::cv_status::timeout) {
+ return -ETIMEDOUT;
+ }
+ }
+ m_notified = false;
+ return 0;
+ }
+
+ void expect_local_journal_add_listener(
+ librbd::MockTestJournal& mock_local_journal,
+ librbd::journal::Listener** local_journal_listener) {
+ EXPECT_CALL(mock_local_journal, add_listener(_))
+ .WillOnce(SaveArg<0>(local_journal_listener));
+ expect_is_tag_owner(mock_local_journal, false);
+ expect_is_resync_requested(mock_local_journal, 0, false);
+ }
+
+ int init_entry_replayer(MockReplayer& mock_replayer,
+ MockThreads& mock_threads,
+ MockReplayerListener& mock_replayer_listener,
+ librbd::MockTestJournal& mock_local_journal,
+ ::journal::MockJournaler& mock_remote_journaler,
+ MockReplay& mock_local_journal_replay,
+ librbd::journal::Listener** local_journal_listener,
+ ::journal::ReplayHandler** remote_replay_handler,
+ ::journal::JournalMetadataListener** remote_journal_listener) {
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_))
+ .WillOnce(SaveArg<0>(remote_journal_listener));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid", {},
+ {librbd::journal::MirrorPeerClientMeta{}}, 0);
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ local_journal_listener);
+ EXPECT_CALL(mock_remote_journaler, start_live_replay(_, _))
+ .WillOnce(SaveArg<0>(remote_replay_handler));
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ int r = init_ctx.wait();
+ if (r < 0) {
+ return r;
+ }
+
+ return wait_for_notification();
+ }
+
+ int shut_down_entry_replayer(MockReplayer& mock_replayer,
+ MockThreads& mock_threads,
+ librbd::MockTestJournal& mock_local_journal,
+ ::journal::MockJournaler& mock_remote_journaler,
+ MockReplay& mock_local_journal_replay) {
+ expect_shut_down(mock_local_journal_replay, true, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ expect_stop_replay(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ return shutdown_ctx.wait();
+ }
+
+ librbd::ImageCtx* m_local_image_ctx = nullptr;
+
+ ceph::mutex m_lock = ceph::make_mutex(
+ "TestMockImageReplayerJournalReplayer");
+ ceph::condition_variable m_cond;
+ bool m_notified = false;
+};
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitShutDown) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitRemoteJournalerError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, -EINVAL);
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-EINVAL, init_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitRemoteJournalerGetClientError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid", {},
+ {librbd::journal::MirrorPeerClientMeta{}}, -EINVAL);
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-EINVAL, init_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitNoLocalJournal) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+
+ mock_local_image_ctx.journal = nullptr;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid", {},
+ {librbd::journal::MirrorPeerClientMeta{}}, 0);
+
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-EINVAL, init_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitLocalJournalStartExternalReplayError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid", {},
+ {librbd::journal::MirrorPeerClientMeta{}}, 0);
+ expect_start_external_replay(mock_local_journal, nullptr, -EINVAL);
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-EINVAL, init_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitIsPromoted) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid", {},
+ {librbd::journal::MirrorPeerClientMeta{}}, 0);
+ MockReplay mock_local_journal_replay;
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ EXPECT_CALL(mock_local_journal, add_listener(_));
+ expect_is_tag_owner(mock_local_journal, true);
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(0, init_ctx.wait());
+ ASSERT_EQ(0, wait_for_notification());
+
+ expect_shut_down(mock_local_journal_replay, true, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitDisconnected) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ mock_local_image_ctx.config.set_val("rbd_mirroring_resync_after_disconnect",
+ "false");
+
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid",
+ {{}, {}, {},
+ cls::journal::CLIENT_STATE_DISCONNECTED},
+ {librbd::journal::MirrorPeerClientMeta{
+ mock_local_image_ctx.id}}, 0);
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-ENOTCONN, init_ctx.wait());
+ ASSERT_FALSE(mock_replayer.is_resync_requested());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitDisconnectedResync) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ mock_local_image_ctx.config.set_val("rbd_mirroring_resync_after_disconnect",
+ "true");
+
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid",
+ {{}, {}, {},
+ cls::journal::CLIENT_STATE_DISCONNECTED},
+ {librbd::journal::MirrorPeerClientMeta{
+ mock_local_image_ctx.id}}, 0);
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-ENOTCONN, init_ctx.wait());
+ ASSERT_TRUE(mock_replayer.is_resync_requested());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitResyncRequested) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid", {},
+ {librbd::journal::MirrorPeerClientMeta{}}, 0);
+ MockReplay mock_local_journal_replay;
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ EXPECT_CALL(mock_local_journal, add_listener(_));
+ expect_is_tag_owner(mock_local_journal, false);
+ expect_is_resync_requested(mock_local_journal, 0, true);
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(0, init_ctx.wait());
+ ASSERT_EQ(0, wait_for_notification());
+
+ expect_shut_down(mock_local_journal_replay, true, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, InitResyncRequestedError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ expect_init(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, add_listener(_));
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid", {},
+ {librbd::journal::MirrorPeerClientMeta{}}, 0);
+ MockReplay mock_local_journal_replay;
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ EXPECT_CALL(mock_local_journal, add_listener(_));
+ expect_is_tag_owner(mock_local_journal, false);
+ expect_is_resync_requested(mock_local_journal, -EINVAL, false);
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(0, init_ctx.wait());
+ ASSERT_EQ(0, wait_for_notification());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ expect_shut_down(mock_local_journal_replay, true, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, ShutDownLocalJournalReplayError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_shut_down(mock_local_journal_replay, true, -EINVAL);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ expect_stop_replay(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(-EINVAL, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, CloseLocalImageError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_shut_down(mock_local_journal_replay, true, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, -EINVAL);
+ expect_stop_replay(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(-EINVAL, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, StopRemoteJournalerError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_shut_down(mock_local_journal_replay, true, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ expect_stop_replay(mock_remote_journaler, -EPERM);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(-EPERM, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, Replay) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+ expect_committed(mock_replay_entry, mock_remote_journaler, 2);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+
+ // replay_flush
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_allocate_tag(mock_local_journal, 0);
+
+ // process
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _)).WillOnce(Return(0));
+ expect_preprocess(mock_event_preprocessor, false, 0);
+ expect_process(mock_local_journal_replay, 0, 0);
+ EXPECT_CALL(mock_replay_status_formatter, handle_entry_processed(_));
+
+ // the next event with preprocess
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _)).WillOnce(Return(0));
+ expect_preprocess(mock_event_preprocessor, true, 0);
+ expect_process(mock_local_journal_replay, 0, 0);
+ EXPECT_CALL(mock_replay_status_formatter, handle_entry_processed(_));
+
+ // attempt to process the next event
+ C_SaferCond replay_ctx;
+ expect_try_pop_front_return_no_entries(mock_remote_journaler, &replay_ctx);
+
+ // fire
+ remote_replay_handler->handle_entries_available();
+ ASSERT_EQ(0, replay_ctx.wait());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, DecodeError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+
+ // replay_flush
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_allocate_tag(mock_local_journal, 0);
+
+ // process
+ EXPECT_CALL(mock_replay_entry, get_data());
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _))
+ .WillOnce(Return(-EINVAL));
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ // fire
+ remote_replay_handler->handle_entries_available();
+ wait_for_notification();
+
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, DelayedReplay) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+ expect_committed(mock_replay_entry, mock_remote_journaler, 1);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+
+ // replay_flush
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_allocate_tag(mock_local_journal, 0);
+
+ // process with delay
+ EXPECT_CALL(mock_replay_entry, get_data());
+ librbd::journal::EventEntry event_entry(
+ librbd::journal::AioDiscardEvent(123, 345, 0), ceph_clock_now());
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(event_entry),
+ Return(0)));
+
+ Context* delayed_task_ctx = nullptr;
+ EXPECT_CALL(*mock_threads.timer, add_event_after(_, _))
+ .WillOnce(
+ DoAll(Invoke([this, &delayed_task_ctx](double seconds, Context *ctx) {
+ std::unique_lock locker{m_lock};
+ delayed_task_ctx = ctx;
+ m_cond.notify_all();
+ }),
+ ReturnArg<1>()));
+ expect_preprocess(mock_event_preprocessor, false, 0);
+ expect_process(mock_local_journal_replay, 0, 0);
+ EXPECT_CALL(mock_replay_status_formatter, handle_entry_processed(_));
+
+ // attempt to process the next event
+ C_SaferCond replay_ctx;
+ expect_try_pop_front_return_no_entries(mock_remote_journaler, &replay_ctx);
+
+ // fire
+ mock_local_image_ctx.mirroring_replay_delay = 600;
+ remote_replay_handler->handle_entries_available();
+ {
+ std::unique_lock locker{m_lock};
+ while (delayed_task_ctx == nullptr) {
+ if (m_cond.wait_for(locker, 10s) == std::cv_status::timeout) {
+ FAIL() << "timed out waiting for task";
+ break;
+ }
+ }
+ }
+ {
+ std::unique_lock timer_locker{mock_threads.timer_lock};
+ delayed_task_ctx->complete(0);
+ }
+ ASSERT_EQ(0, replay_ctx.wait());
+
+ // add a pending (delayed) entry before stop
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ C_SaferCond decode_ctx;
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _))
+ .WillOnce(DoAll(Invoke([&decode_ctx](bufferlist::const_iterator* it,
+ librbd::journal::EventEntry *e) {
+ decode_ctx.complete(0);
+ }),
+ Return(0)));
+
+ remote_replay_handler->handle_entries_available();
+ ASSERT_EQ(0, decode_ctx.wait());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, ReplayNoMemoryError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_notification(mock_threads, mock_replayer_listener);
+ remote_replay_handler->handle_complete(-ENOMEM);
+
+ wait_for_notification();
+ ASSERT_EQ(false, mock_replayer.is_replaying());
+ ASSERT_EQ(-ENOMEM, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, LocalJournalForcePromoted) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_notification(mock_threads, mock_replayer_listener);
+ local_journal_listener->handle_promoted();
+ wait_for_notification();
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, LocalJournalResyncRequested) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_notification(mock_threads, mock_replayer_listener);
+ local_journal_listener->handle_resync();
+ wait_for_notification();
+
+ ASSERT_TRUE(mock_replayer.is_resync_requested());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, RemoteJournalDisconnected) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ mock_local_image_ctx.config.set_val("rbd_mirroring_resync_after_disconnect",
+ "true");
+
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_get_cached_client(mock_remote_journaler, "local mirror uuid",
+ {{}, {}, {},
+ cls::journal::CLIENT_STATE_DISCONNECTED},
+ {librbd::journal::MirrorPeerClientMeta{
+ mock_local_image_ctx.id}}, 0);
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ remote_journaler_listener->handle_update(nullptr);
+ wait_for_notification();
+
+ ASSERT_EQ(-ENOTCONN, mock_replayer.get_error_code());
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_TRUE(mock_replayer.is_resync_requested());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, Flush) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_flush(mock_local_journal_replay, 0);
+ expect_flush_commit_position(mock_remote_journaler, 0);
+
+ C_SaferCond ctx;
+ mock_replayer.flush(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, FlushError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_flush(mock_local_journal_replay, -EINVAL);
+
+ C_SaferCond ctx;
+ mock_replayer.flush(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, FlushCommitPositionError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_flush(mock_local_journal_replay, 0);
+ expect_flush_commit_position(mock_remote_journaler, -EINVAL);
+
+ C_SaferCond ctx;
+ mock_replayer.flush(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+
+TEST_F(TestMockImageReplayerJournalReplayer, ReplayFlushShutDownError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_try_pop_front(mock_remote_journaler, 1, true);
+ expect_shut_down(mock_local_journal_replay, false, -EINVAL);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_notification(mock_threads, mock_replayer_listener);
+ remote_replay_handler->handle_entries_available();
+
+ wait_for_notification();
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ expect_stop_replay(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, ReplayFlushStartError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ expect_try_pop_front(mock_remote_journaler, 1, true);
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, nullptr, -EINVAL);
+ expect_notification(mock_threads, mock_replayer_listener);
+ remote_replay_handler->handle_entries_available();
+
+ wait_for_notification();
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ MockCloseImageRequest mock_close_image_request;
+ expect_send(mock_close_image_request, 0);
+ expect_stop_replay(mock_remote_journaler, 0);
+ EXPECT_CALL(mock_remote_journaler, remove_listener(_));
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, GetTagError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, -EINVAL);
+ expect_notification(mock_threads, mock_replayer_listener);
+ remote_replay_handler->handle_entries_available();
+
+ wait_for_notification();
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, AllocateTagDemotion) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_notification(mock_threads, mock_replayer_listener);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+ expect_committed(mock_replay_entry, mock_remote_journaler, 1);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::ORPHAN_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_get_tag_data(mock_local_journal, {});
+ expect_allocate_tag(mock_local_journal, 0);
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _)).WillOnce(Return(0));
+ expect_preprocess(mock_event_preprocessor, false, 0);
+ expect_process(mock_local_journal_replay, 0, 0);
+ EXPECT_CALL(mock_replay_status_formatter, handle_entry_processed(_));
+
+ remote_replay_handler->handle_entries_available();
+ wait_for_notification();
+ ASSERT_FALSE(mock_replayer.is_replaying());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, AllocateTagError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_allocate_tag(mock_local_journal, -EINVAL);
+ expect_notification(mock_threads, mock_replayer_listener);
+ remote_replay_handler->handle_entries_available();
+
+ wait_for_notification();
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, PreprocessError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_allocate_tag(mock_local_journal, 0);
+ EXPECT_CALL(mock_replay_entry, get_data());
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _)).WillOnce(Return(0));
+ expect_preprocess(mock_event_preprocessor, true, -EINVAL);
+
+ expect_notification(mock_threads, mock_replayer_listener);
+ remote_replay_handler->handle_entries_available();
+
+ wait_for_notification();
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, ProcessError) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_allocate_tag(mock_local_journal, 0);
+ EXPECT_CALL(mock_replay_entry, get_data());
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _)).WillOnce(Return(0));
+ expect_preprocess(mock_event_preprocessor, false, 0);
+ expect_process(mock_local_journal_replay, 0, -EINVAL);
+ EXPECT_CALL(mock_replay_status_formatter, handle_entry_processed(_));
+
+ // attempt to process the next event
+ C_SaferCond replay_ctx;
+ expect_try_pop_front_return_no_entries(mock_remote_journaler, &replay_ctx);
+ remote_replay_handler->handle_entries_available();
+
+ wait_for_notification();
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, replay_ctx.wait());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+TEST_F(TestMockImageReplayerJournalReplayer, ImageNameUpdated) {
+ librbd::MockTestJournal mock_local_journal;
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx,
+ mock_local_journal};
+ ::journal::MockJournaler mock_remote_journaler;
+ MockReplayerListener mock_replayer_listener;
+ MockThreads mock_threads{m_threads};
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_journaler,
+ {});
+ MockReplayer mock_replayer{
+ &mock_threads, "local mirror uuid", &mock_state_builder,
+ &mock_replayer_listener};
+
+ ::journal::MockReplayEntry mock_replay_entry;
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+ expect_get_commit_tid_in_debug(mock_replay_entry);
+ expect_get_tag_tid_in_debug(mock_local_journal);
+ expect_committed(mock_replay_entry, mock_remote_journaler, 1);
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockReplay mock_local_journal_replay;
+ MockEventPreprocessor mock_event_preprocessor;
+ MockReplayStatusFormatter mock_replay_status_formatter;
+ librbd::journal::Listener* local_journal_listener = nullptr;
+ ::journal::ReplayHandler* remote_replay_handler = nullptr;
+ ::journal::JournalMetadataListener* remote_journaler_listener = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_replayer_listener, mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay,
+ &local_journal_listener,
+ &remote_replay_handler,
+ &remote_journaler_listener));
+
+ mock_local_image_ctx.name = "NEW NAME";
+ cls::journal::Tag tag =
+ {1, 0, encode_tag_data({librbd::Journal<>::LOCAL_MIRROR_UUID,
+ librbd::Journal<>::LOCAL_MIRROR_UUID,
+ true, 0, 0})};
+
+ expect_try_pop_front(mock_remote_journaler, tag.tid, true);
+ expect_shut_down(mock_local_journal_replay, false, 0);
+ EXPECT_CALL(mock_local_journal, remove_listener(_));
+ EXPECT_CALL(mock_local_journal, stop_external_replay());
+ expect_start_external_replay(mock_local_journal, &mock_local_journal_replay,
+ 0);
+ expect_local_journal_add_listener(mock_local_journal,
+ &local_journal_listener);
+ expect_get_tag(mock_remote_journaler, tag, 0);
+ expect_allocate_tag(mock_local_journal, 0);
+ EXPECT_CALL(mock_local_journal_replay, decode(_, _)).WillOnce(Return(0));
+ expect_preprocess(mock_event_preprocessor, false, 0);
+ expect_process(mock_local_journal_replay, 0, 0);
+ EXPECT_CALL(mock_replay_status_formatter, handle_entry_processed(_));
+
+ // attempt to process the next event
+ C_SaferCond replay_ctx;
+ expect_try_pop_front_return_no_entries(mock_remote_journaler, &replay_ctx);
+
+ remote_replay_handler->handle_entries_available();
+ wait_for_notification();
+
+ auto image_spec = util::compute_image_spec(m_local_io_ctx, "NEW NAME");
+ ASSERT_EQ(image_spec, mock_replayer.get_image_spec());
+
+ ASSERT_EQ(0, replay_ctx.wait());
+ ASSERT_TRUE(mock_replayer.is_replaying());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_journal,
+ mock_remote_journaler,
+ mock_local_journal_replay));
+}
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
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 000000000..904d36854
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/snapshot/test_mock_ApplyImageStateRequest.cc
@@ -0,0 +1,641 @@
+// -*- 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<MockTestImageCtx> {
+ std::map<std::string, bufferlist>* pairs = nullptr;
+ Context* on_finish = nullptr;
+
+ static GetMetadataRequest* s_instance;
+ static GetMetadataRequest* create(librados::IoCtx& io_ctx,
+ const std::string& oid,
+ bool filter_internal,
+ const std::string& filter_key_prefix,
+ const std::string& last_key,
+ size_t max_results,
+ std::map<std::string, bufferlist>* 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<MockTestImageCtx>* GetMetadataRequest<MockTestImageCtx>::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<librbd::MockTestImageCtx> MockApplyImageStateRequest;
+ typedef librbd::image::GetMetadataRequest<librbd::MockTestImageCtx> 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<std::string, bufferlist>& 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<std::string>& remove,
+ const std::map<std::string, bufferlist>& 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/test/rbd_mirror/image_replayer/snapshot/test_mock_CreateLocalImageRequest.cc b/src/test/rbd_mirror/image_replayer/snapshot/test_mock_CreateLocalImageRequest.cc
new file mode 100644
index 000000000..58214f3e8
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/snapshot/test_mock_CreateLocalImageRequest.cc
@@ -0,0 +1,356 @@
+// -*- 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/ImageState.h"
+#include "librbd/Operations.h"
+#include "tools/rbd_mirror/PoolMetaCache.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/CreateImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/snapshot/CreateLocalImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/snapshot/StateBuilder.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/rbd_mirror/mock/MockContextWQ.h"
+#include "test/rbd_mirror/mock/MockSafeTimer.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace util {
+
+static std::string s_image_id;
+
+template <>
+std::string generate_image_id<MockTestImageCtx>(librados::IoCtx&) {
+ ceph_assert(!s_image_id.empty());
+ return s_image_id;
+}
+
+} // namespace util
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+template <>
+struct Threads<librbd::MockTestImageCtx> {
+ ceph::mutex &timer_lock;
+ SafeTimer *timer;
+ librbd::asio::ContextWQ *work_queue;
+
+ Threads(Threads<librbd::ImageCtx> *threads)
+ : timer_lock(threads->timer_lock), timer(threads->timer),
+ work_queue(threads->work_queue) {
+ }
+};
+
+namespace image_replayer {
+
+template<>
+struct CreateImageRequest<librbd::MockTestImageCtx> {
+ static CreateImageRequest* s_instance;
+ Context *on_finish = nullptr;
+
+ static CreateImageRequest* create(Threads<librbd::MockTestImageCtx>* threads,
+ librados::IoCtx &local_io_ctx,
+ const std::string &global_image_id,
+ const std::string &remote_mirror_uuid,
+ const std::string &local_image_name,
+ const std::string &local_image_id,
+ librbd::MockTestImageCtx *remote_image_ctx,
+ PoolMetaCache* pool_meta_cache,
+ cls::rbd::MirrorImageMode mirror_image_mode,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ s_instance->construct(local_image_id);
+ return s_instance;
+ }
+
+ CreateImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~CreateImageRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD1(construct, void(const std::string&));
+ MOCK_METHOD0(send, void());
+};
+
+CreateImageRequest<librbd::MockTestImageCtx>*
+ CreateImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace snapshot {
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ std::string local_image_id;
+ std::string remote_mirror_uuid;
+};
+
+} // namespace snapshot
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+#include "tools/rbd_mirror/image_replayer/snapshot/CreateLocalImageRequest.cc"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace snapshot {
+
+class TestMockImageReplayerSnapshotCreateLocalImageRequest : public TestMockFixture {
+public:
+ typedef CreateLocalImageRequest<librbd::MockTestImageCtx> MockCreateLocalImageRequest;
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef CreateImageRequest<librbd::MockTestImageCtx> MockCreateImageRequest;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::RBD rbd;
+ 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_remote_image_ctx = new librbd::MockTestImageCtx(*m_remote_image_ctx);
+ }
+
+ void TearDown() override {
+ delete m_mock_remote_image_ctx;
+ TestMockFixture::TearDown();
+ }
+
+ void snap_create(librbd::ImageCtx *image_ctx, const std::string &snap_name) {
+ librbd::NoOpProgressContext prog_ctx;
+ ASSERT_EQ(0, image_ctx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ snap_name, 0, prog_ctx));
+ ASSERT_EQ(0, image_ctx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(),
+ snap_name));
+ ASSERT_EQ(0, image_ctx->state->refresh());
+ }
+
+ int clone_image(librbd::ImageCtx *parent_image_ctx,
+ const std::string &snap_name, const std::string &clone_name) {
+ snap_create(parent_image_ctx, snap_name);
+
+ int order = 0;
+ return librbd::clone(m_remote_io_ctx, parent_image_ctx->name.c_str(),
+ snap_name.c_str(), m_remote_io_ctx,
+ clone_name.c_str(), parent_image_ctx->features,
+ &order, 0, 0);
+ }
+
+ void expect_mirror_image_set(const std::string& image_id,
+ const cls::rbd::MirrorImage& mirror_image,
+ int r) {
+ bufferlist bl;
+ encode(image_id, bl);
+ encode(mirror_image, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(m_local_io_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"),
+ StrEq("mirror_image_set"), ContentsEqual(bl), _, _, _))
+ .WillOnce(Return(r));
+ }
+
+ void expect_mirror_image_remove(const std::string& image_id, int r) {
+ bufferlist bl;
+ encode(image_id, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(m_local_io_ctx),
+ exec(StrEq("rbd_mirroring"), _, StrEq("rbd"),
+ StrEq("mirror_image_remove"),
+ ContentsEqual(bl), _, _, _))
+ .WillOnce(Return(r));
+ }
+
+ void expect_create_image(MockCreateImageRequest& mock_create_image_request,
+ const std::string& image_id, int r) {
+ EXPECT_CALL(mock_create_image_request, construct(image_id));
+ EXPECT_CALL(mock_create_image_request, send())
+ .WillOnce(Invoke([this, &mock_create_image_request, r]() {
+ m_threads->work_queue->queue(mock_create_image_request.on_finish, r);
+ }));
+ }
+
+ MockCreateLocalImageRequest* create_request(
+ MockThreads& mock_threads,
+ MockStateBuilder& mock_state_builder,
+ const std::string& global_image_id,
+ Context* on_finish) {
+ return new MockCreateLocalImageRequest(
+ &mock_threads, m_local_io_ctx, m_mock_remote_image_ctx,
+ global_image_id, &m_pool_meta_cache, nullptr, &mock_state_builder,
+ on_finish);
+ }
+
+ PoolMetaCache m_pool_meta_cache{g_ceph_context};
+
+ librbd::ImageCtx *m_remote_image_ctx;
+ librbd::MockTestImageCtx *m_mock_remote_image_ctx = nullptr;
+};
+
+TEST_F(TestMockImageReplayerSnapshotCreateLocalImageRequest, Success) {
+ InSequence seq;
+
+ librbd::util::s_image_id = "local image id";
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_CREATING}, 0);
+
+ MockCreateImageRequest mock_create_image_request;
+ expect_create_image(mock_create_image_request, "local image id", 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ ASSERT_EQ("local image id", mock_state_builder.local_image_id);
+}
+
+TEST_F(TestMockImageReplayerSnapshotCreateLocalImageRequest, AddMirrorImageError) {
+ InSequence seq;
+
+ librbd::util::s_image_id = "local image id";
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_CREATING}, -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotCreateLocalImageRequest, CreateImageError) {
+ InSequence seq;
+
+ librbd::util::s_image_id = "local image id";
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_CREATING}, 0);
+
+ MockCreateImageRequest mock_create_image_request;
+ expect_create_image(mock_create_image_request, "local image id", -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotCreateLocalImageRequest, CreateImageDuplicate) {
+ InSequence seq;
+
+ librbd::util::s_image_id = "local image id";
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_CREATING}, 0);
+
+ MockCreateImageRequest mock_create_image_request;
+ expect_create_image(mock_create_image_request, "local image id", -EBADF);
+
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_DISABLING}, 0);
+
+ expect_mirror_image_remove("local image id", 0);
+
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_CREATING}, 0);
+
+ expect_create_image(mock_create_image_request, "local image id", 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockStateBuilder mock_state_builder;
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+
+ ASSERT_EQ("local image id", mock_state_builder.local_image_id);
+}
+
+TEST_F(TestMockImageReplayerSnapshotCreateLocalImageRequest, DisableMirrorImageError) {
+ InSequence seq;
+
+ librbd::util::s_image_id = "local image id";
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_DISABLING}, -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockStateBuilder mock_state_builder;
+ mock_state_builder.local_image_id = "local image id";
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotCreateLocalImageRequest, RemoveMirrorImageError) {
+ InSequence seq;
+
+ librbd::util::s_image_id = "local image id";
+ expect_mirror_image_set("local image id",
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_DISABLING}, 0);
+
+ expect_mirror_image_remove("local image id", -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockStateBuilder mock_state_builder;
+ mock_state_builder.local_image_id = "local image id";
+ auto request = create_request(
+ mock_threads, mock_state_builder, "global image id", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace journal
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/snapshot/test_mock_Replayer.cc b/src/test/rbd_mirror/image_replayer/snapshot/test_mock_Replayer.cc
new file mode 100644
index 000000000..75141e5a7
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/snapshot/test_mock_Replayer.cc
@@ -0,0 +1,3327 @@
+// -*- 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/deep_copy/ImageCopyRequest.h"
+#include "librbd/deep_copy/SnapshotCopyRequest.h"
+#include "librbd/mirror/ImageStateUpdateRequest.h"
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h"
+#include "librbd/mirror/snapshot/GetImageStateRequest.h"
+#include "librbd/mirror/snapshot/ImageMeta.h"
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+#include "tools/rbd_mirror/InstanceWatcher.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/CloseImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/ReplayerListener.h"
+#include "tools/rbd_mirror/image_replayer/Utils.h"
+#include "tools/rbd_mirror/image_replayer/snapshot/ApplyImageStateRequest.h"
+#include "tools/rbd_mirror/image_replayer/snapshot/Replayer.h"
+#include "tools/rbd_mirror/image_replayer/snapshot/StateBuilder.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockOperations.h"
+#include "test/rbd_mirror/mock/MockContextWQ.h"
+#include "test/rbd_mirror/mock/MockSafeTimer.h"
+
+using namespace std::chrono_literals;
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace deep_copy {
+
+template <>
+struct ImageCopyRequest<MockTestImageCtx> {
+ uint64_t src_snap_id_start;
+ uint64_t src_snap_id_end;
+ uint64_t dst_snap_id_start;
+ librbd::deep_copy::ObjectNumber object_number;
+ librbd::SnapSeqs snap_seqs;
+
+ static ImageCopyRequest* s_instance;
+ static ImageCopyRequest* create(MockTestImageCtx *src_image_ctx,
+ MockTestImageCtx *dst_image_ctx,
+ librados::snap_t src_snap_id_start,
+ librados::snap_t src_snap_id_end,
+ librados::snap_t dst_snap_id_start,
+ bool flatten,
+ const ObjectNumber &object_number,
+ const SnapSeqs &snap_seqs,
+ Handler *handler,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->src_snap_id_start = src_snap_id_start;
+ s_instance->src_snap_id_end = src_snap_id_end;
+ s_instance->dst_snap_id_start = dst_snap_id_start;
+ s_instance->object_number = object_number;
+ s_instance->snap_seqs = snap_seqs;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ Context* on_finish = nullptr;
+
+ ImageCopyRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+struct SnapshotCopyRequest<MockTestImageCtx> {
+ librados::snap_t src_snap_id_start;
+ librados::snap_t src_snap_id_end;
+ librados::snap_t dst_snap_id_start;
+ SnapSeqs* snap_seqs = nullptr;
+
+ static SnapshotCopyRequest* s_instance;
+ static SnapshotCopyRequest* create(MockTestImageCtx *src_image_ctx,
+ MockTestImageCtx *dst_image_ctx,
+ librados::snap_t src_snap_id_start,
+ librados::snap_t src_snap_id_end,
+ librados::snap_t dst_snap_id_start,
+ bool flatten,
+ ::MockContextWQ *work_queue,
+ SnapSeqs *snap_seqs,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->src_snap_id_start = src_snap_id_start;
+ s_instance->src_snap_id_end = src_snap_id_end;
+ s_instance->dst_snap_id_start = dst_snap_id_start;
+ s_instance->snap_seqs = snap_seqs;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ Context* on_finish = nullptr;
+
+ SnapshotCopyRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+ImageCopyRequest<MockTestImageCtx>* ImageCopyRequest<MockTestImageCtx>::s_instance = nullptr;
+SnapshotCopyRequest<MockTestImageCtx>* SnapshotCopyRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+
+namespace mirror {
+
+template <>
+struct ImageStateUpdateRequest<MockTestImageCtx> {
+ static ImageStateUpdateRequest* s_instance;
+ static ImageStateUpdateRequest* create(
+ librados::IoCtx& io_ctx,
+ const std::string& image_id,
+ cls::rbd::MirrorImageState mirror_image_state,
+ const cls::rbd::MirrorImage& mirror_image,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ EXPECT_EQ(cls::rbd::MIRROR_IMAGE_STATE_ENABLED,
+ mirror_image_state);
+ EXPECT_EQ(cls::rbd::MirrorImage{}, mirror_image);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ Context* on_finish = nullptr;
+ ImageStateUpdateRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+ImageStateUpdateRequest<MockTestImageCtx>* ImageStateUpdateRequest<MockTestImageCtx>::s_instance = nullptr;
+
+namespace snapshot {
+
+template <>
+struct CreateNonPrimaryRequest<MockTestImageCtx> {
+ bool demoted = false;
+ std::string primary_mirror_uuid;
+ uint64_t primary_snap_id;
+ SnapSeqs snap_seqs;
+ uint64_t* snap_id = nullptr;
+
+ static CreateNonPrimaryRequest* s_instance;
+ static CreateNonPrimaryRequest* create(MockTestImageCtx *image_ctx,
+ bool demoted,
+ const std::string &primary_mirror_uuid,
+ uint64_t primary_snap_id,
+ const SnapSeqs& snap_seqs,
+ const ImageState &image_state,
+ uint64_t *snap_id,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->demoted = demoted;
+ s_instance->primary_mirror_uuid = primary_mirror_uuid;
+ s_instance->primary_snap_id = primary_snap_id;
+ s_instance->snap_seqs = snap_seqs;
+ s_instance->snap_id = snap_id;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ Context* on_finish = nullptr;
+
+ CreateNonPrimaryRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+struct GetImageStateRequest<MockTestImageCtx> {
+ uint64_t snap_id = CEPH_NOSNAP;
+
+ static GetImageStateRequest* s_instance;
+ static GetImageStateRequest* create(MockTestImageCtx *image_ctx,
+ uint64_t snap_id,
+ ImageState *image_state,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->snap_id = snap_id;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ Context* on_finish = nullptr;
+
+ GetImageStateRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+struct ImageMeta<MockTestImageCtx> {
+ MOCK_METHOD1(load, void(Context*));
+
+ bool resync_requested = false;
+};
+
+template <>
+struct UnlinkPeerRequest<MockTestImageCtx> {
+ uint64_t snap_id;
+ std::string mirror_peer_uuid;
+ bool allow_remove;
+
+ static UnlinkPeerRequest* s_instance;
+ static UnlinkPeerRequest*create (MockTestImageCtx *image_ctx,
+ uint64_t snap_id,
+ const std::string &mirror_peer_uuid,
+ bool allow_remove,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->snap_id = snap_id;
+ s_instance->mirror_peer_uuid = mirror_peer_uuid;
+ s_instance->allow_remove = allow_remove;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ Context* on_finish = nullptr;
+
+ UnlinkPeerRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+CreateNonPrimaryRequest<MockTestImageCtx>* CreateNonPrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+GetImageStateRequest<MockTestImageCtx>* GetImageStateRequest<MockTestImageCtx>::s_instance = nullptr;
+UnlinkPeerRequest<MockTestImageCtx>* UnlinkPeerRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+template <>
+struct InstanceWatcher<librbd::MockTestImageCtx> {
+ MOCK_METHOD1(cancel_sync_request, void(const std::string&));
+ MOCK_METHOD2(notify_sync_request, void(const std::string&,
+ Context*));
+ MOCK_METHOD1(notify_sync_complete, void(const std::string&));
+};
+
+template <>
+struct Threads<librbd::MockTestImageCtx> {
+ MockSafeTimer *timer;
+ ceph::mutex &timer_lock;
+
+ MockContextWQ *work_queue;
+
+ Threads(Threads<librbd::ImageCtx>* threads)
+ : timer(new MockSafeTimer()),
+ timer_lock(threads->timer_lock),
+ work_queue(new MockContextWQ()) {
+ }
+ ~Threads() {
+ delete timer;
+ delete work_queue;
+ }
+};
+
+namespace {
+
+struct MockReplayerListener : public image_replayer::ReplayerListener {
+ MOCK_METHOD0(handle_notification, void());
+};
+
+} // anonymous namespace
+
+namespace image_replayer {
+
+template<>
+struct CloseImageRequest<librbd::MockTestImageCtx> {
+ static CloseImageRequest* s_instance;
+ librbd::MockTestImageCtx **image_ctx = nullptr;
+ Context *on_finish = nullptr;
+
+ static CloseImageRequest* create(librbd::MockTestImageCtx **image_ctx,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->image_ctx = image_ctx;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ CloseImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+
+ ~CloseImageRequest() {
+ ceph_assert(s_instance == this);
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+CloseImageRequest<librbd::MockTestImageCtx>* CloseImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace snapshot {
+
+template <>
+struct ApplyImageStateRequest<librbd::MockTestImageCtx> {
+ Context* on_finish = nullptr;
+
+ static ApplyImageStateRequest* s_instance;
+ static ApplyImageStateRequest* create(
+ const std::string& local_mirror_uuid,
+ const std::string& remote_mirror_uuid,
+ librbd::MockTestImageCtx* local_image_ctx,
+ librbd::MockTestImageCtx* remote_image_ctx,
+ const librbd::mirror::snapshot::ImageState& image_state,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ ApplyImageStateRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ StateBuilder(librbd::MockTestImageCtx& local_image_ctx,
+ librbd::MockTestImageCtx& remote_image_ctx,
+ librbd::mirror::snapshot::ImageMeta<librbd::MockTestImageCtx>&
+ local_image_meta)
+ : local_image_ctx(&local_image_ctx),
+ remote_image_ctx(&remote_image_ctx),
+ local_image_meta(&local_image_meta) {
+ }
+
+ librbd::MockTestImageCtx* local_image_ctx;
+ librbd::MockTestImageCtx* remote_image_ctx;
+
+ std::string remote_mirror_uuid = "remote mirror uuid";
+
+ librbd::mirror::snapshot::ImageMeta<librbd::MockTestImageCtx>*
+ local_image_meta = nullptr;
+};
+
+ApplyImageStateRequest<librbd::MockTestImageCtx>* ApplyImageStateRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+#include "tools/rbd_mirror/image_replayer/snapshot/Replayer.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::ReturnArg;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockImageReplayerSnapshotReplayer : public TestMockFixture {
+public:
+ typedef Replayer<librbd::MockTestImageCtx> MockReplayer;
+ typedef ApplyImageStateRequest<librbd::MockTestImageCtx> MockApplyImageStateRequest;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+ typedef InstanceWatcher<librbd::MockTestImageCtx> MockInstanceWatcher;
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef CloseImageRequest<librbd::MockTestImageCtx> MockCloseImageRequest;
+ typedef librbd::deep_copy::ImageCopyRequest<librbd::MockTestImageCtx> MockImageCopyRequest;
+ typedef librbd::deep_copy::SnapshotCopyRequest<librbd::MockTestImageCtx> MockSnapshotCopyRequest;
+ typedef librbd::mirror::ImageStateUpdateRequest<librbd::MockTestImageCtx> MockImageStateUpdateRequest;
+ typedef librbd::mirror::snapshot::CreateNonPrimaryRequest<librbd::MockTestImageCtx> MockCreateNonPrimaryRequest;
+ typedef librbd::mirror::snapshot::GetImageStateRequest<librbd::MockTestImageCtx> MockGetImageStateRequest;
+ typedef librbd::mirror::snapshot::ImageMeta<librbd::MockTestImageCtx> MockImageMeta;
+ typedef librbd::mirror::snapshot::UnlinkPeerRequest<librbd::MockTestImageCtx> MockUnlinkPeerRequest;
+
+ 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));
+ }
+
+ void expect_work_queue_repeatedly(MockThreads &mock_threads) {
+ EXPECT_CALL(*mock_threads.work_queue, queue(_, _))
+ .WillRepeatedly(Invoke([this](Context *ctx, int r) {
+ m_threads->work_queue->queue(ctx, r);
+ }));
+ }
+
+ void expect_add_event_after_repeatedly(MockThreads &mock_threads) {
+ EXPECT_CALL(*mock_threads.timer, add_event_after(_, _))
+ .WillRepeatedly(
+ DoAll(Invoke([this](double seconds, Context *ctx) {
+ m_threads->timer->add_event_after(seconds, ctx);
+ }),
+ ReturnArg<1>()));
+ EXPECT_CALL(*mock_threads.timer, cancel_event(_))
+ .WillRepeatedly(
+ Invoke([this](Context *ctx) {
+ return m_threads->timer->cancel_event(ctx);
+ }));
+ }
+
+ void expect_register_update_watcher(librbd::MockTestImageCtx& mock_image_ctx,
+ librbd::UpdateWatchCtx** update_watch_ctx,
+ uint64_t watch_handle, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, register_update_watcher(_, _))
+ .WillOnce(Invoke([update_watch_ctx, watch_handle, r]
+ (librbd::UpdateWatchCtx* ctx, uint64_t* handle) {
+ if (r >= 0) {
+ *update_watch_ctx = ctx;
+ *handle = watch_handle;
+ }
+ return r;
+ }));
+ }
+
+ void expect_unregister_update_watcher(librbd::MockTestImageCtx& mock_image_ctx,
+ uint64_t watch_handle, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, unregister_update_watcher(watch_handle, _))
+ .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) {
+ m_threads->work_queue->queue(ctx, r);
+ })));
+ }
+
+ void expect_load_image_meta(MockImageMeta& mock_image_meta,
+ bool resync_requested, int r) {
+ EXPECT_CALL(mock_image_meta, load(_))
+ .WillOnce(Invoke([this, &mock_image_meta, resync_requested, r](Context* ctx) {
+ mock_image_meta.resync_requested = resync_requested;
+ m_threads->work_queue->queue(ctx, r);
+ }));
+ }
+
+ void expect_is_refresh_required(librbd::MockTestImageCtx& mock_image_ctx,
+ bool is_required) {
+ EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+ .WillOnce(Return(is_required));
+ }
+
+ void expect_refresh(librbd::MockTestImageCtx& mock_image_ctx,
+ const std::map<uint64_t, librbd::SnapInfo>& snaps,
+ int r) {
+ EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+ .WillOnce(Invoke([this, &mock_image_ctx, snaps, r](Context* ctx) {
+ mock_image_ctx.snap_info = snaps;
+ m_threads->work_queue->queue(ctx, r);
+ }));
+ }
+
+ void expect_notify_update(librbd::MockTestImageCtx& mock_image_ctx) {
+ EXPECT_CALL(mock_image_ctx, notify_update(_))
+ .WillOnce(Invoke([this](Context* ctx) {
+ m_threads->work_queue->queue(ctx, 0);
+ }));
+ }
+
+ void expect_prune_non_primary_snapshot(librbd::MockTestImageCtx& mock_image_ctx,
+ uint64_t snap_id, int r) {
+ EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id))
+ .WillOnce(Invoke([&mock_image_ctx](uint64_t snap_id) -> librbd::SnapInfo* {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ if (it == mock_image_ctx.snap_info.end()) {
+ return nullptr;
+ }
+ return &it->second;
+ }));
+ EXPECT_CALL(*mock_image_ctx.operations, snap_remove(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) {
+ m_threads->work_queue->queue(ctx, r);
+ })));
+ }
+
+ void expect_snapshot_copy(MockSnapshotCopyRequest& mock_snapshot_copy_request,
+ uint64_t src_snap_id_start,
+ uint64_t src_snap_id_end,
+ uint64_t dst_snap_id_start,
+ const librbd::SnapSeqs& snap_seqs, int r) {
+ EXPECT_CALL(mock_snapshot_copy_request, send())
+ .WillOnce(Invoke([this, &req=mock_snapshot_copy_request,
+ src_snap_id_start, src_snap_id_end, dst_snap_id_start,
+ snap_seqs, r]() {
+ ASSERT_EQ(src_snap_id_start, req.src_snap_id_start);
+ ASSERT_EQ(src_snap_id_end, req.src_snap_id_end);
+ ASSERT_EQ(dst_snap_id_start, req.dst_snap_id_start);
+ *req.snap_seqs = snap_seqs;
+ m_threads->work_queue->queue(req.on_finish, r);
+ }));
+ }
+
+ void expect_get_image_state(MockGetImageStateRequest& mock_get_image_state_request,
+ uint64_t snap_id, int r) {
+ EXPECT_CALL(mock_get_image_state_request, send())
+ .WillOnce(Invoke([this, &req=mock_get_image_state_request, snap_id, r]() {
+ ASSERT_EQ(snap_id, req.snap_id);
+ m_threads->work_queue->queue(req.on_finish, r);
+ }));
+ }
+
+ void expect_create_non_primary_request(MockCreateNonPrimaryRequest& mock_create_non_primary_request,
+ bool demoted,
+ const std::string& primary_mirror_uuid,
+ uint64_t primary_snap_id,
+ const librbd::SnapSeqs& snap_seqs,
+ uint64_t snap_id, int r) {
+ EXPECT_CALL(mock_create_non_primary_request, send())
+ .WillOnce(Invoke([this, &req=mock_create_non_primary_request, demoted,
+ primary_mirror_uuid, primary_snap_id, snap_seqs,
+ snap_id, r]() {
+ ASSERT_EQ(demoted, req.demoted);
+ ASSERT_EQ(primary_mirror_uuid, req.primary_mirror_uuid);
+ ASSERT_EQ(primary_snap_id, req.primary_snap_id);
+ ASSERT_EQ(snap_seqs, req.snap_seqs);
+ *req.snap_id = snap_id;
+ m_threads->work_queue->queue(req.on_finish, r);
+ }));
+ }
+
+ void expect_update_mirror_image_state(MockImageStateUpdateRequest& mock_image_state_update_request,
+ int r) {
+ EXPECT_CALL(mock_image_state_update_request, send())
+ .WillOnce(Invoke([this, &req=mock_image_state_update_request, r]() {
+ m_threads->work_queue->queue(req.on_finish, r);
+ }));
+ }
+
+ void expect_notify_sync_request(MockInstanceWatcher& mock_instance_watcher,
+ const std::string& image_id, int r) {
+ EXPECT_CALL(mock_instance_watcher, notify_sync_request(image_id, _))
+ .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) {
+ m_threads->work_queue->queue(ctx, r);
+ })));
+ }
+
+ void expect_notify_sync_complete(MockInstanceWatcher& mock_instance_watcher,
+ const std::string& image_id) {
+ EXPECT_CALL(mock_instance_watcher, notify_sync_complete(image_id));
+ }
+
+ void expect_cancel_sync_request(MockInstanceWatcher& mock_instance_watcher,
+ const std::string& image_id) {
+ EXPECT_CALL(mock_instance_watcher, cancel_sync_request(image_id));
+ }
+
+ void expect_image_copy(MockImageCopyRequest& mock_image_copy_request,
+ uint64_t src_snap_id_start, uint64_t src_snap_id_end,
+ uint64_t dst_snap_id_start,
+ const librbd::deep_copy::ObjectNumber& object_number,
+ const librbd::SnapSeqs& snap_seqs, int r) {
+ EXPECT_CALL(mock_image_copy_request, send())
+ .WillOnce(Invoke([this, &req=mock_image_copy_request, src_snap_id_start,
+ src_snap_id_end, dst_snap_id_start, object_number,
+ snap_seqs, r]() {
+ ASSERT_EQ(src_snap_id_start, req.src_snap_id_start);
+ ASSERT_EQ(src_snap_id_end, req.src_snap_id_end);
+ ASSERT_EQ(dst_snap_id_start, req.dst_snap_id_start);
+ ASSERT_EQ(object_number, req.object_number);
+ ASSERT_EQ(snap_seqs, req.snap_seqs);
+ m_threads->work_queue->queue(req.on_finish, r);
+ }));
+ }
+
+ void expect_unlink_peer(MockUnlinkPeerRequest& mock_unlink_peer_request,
+ uint64_t snap_id, const std::string& mirror_peer_uuid,
+ bool allow_remove, int r) {
+ EXPECT_CALL(mock_unlink_peer_request, send())
+ .WillOnce(Invoke([this, &req=mock_unlink_peer_request, snap_id,
+ mirror_peer_uuid, allow_remove, r]() {
+ ASSERT_EQ(snap_id, req.snap_id);
+ ASSERT_EQ(mirror_peer_uuid, req.mirror_peer_uuid);
+ ASSERT_EQ(allow_remove, req.allow_remove);
+ m_threads->work_queue->queue(req.on_finish, r);
+ }));
+ }
+
+ void expect_apply_image_state(
+ MockApplyImageStateRequest& mock_request, int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(Invoke([this, &req=mock_request, r]() {
+ m_threads->work_queue->queue(req.on_finish, r);
+ }));
+ }
+
+ void expect_mirror_image_snapshot_set_copy_progress(
+ librbd::MockTestImageCtx& mock_test_image_ctx, uint64_t snap_id,
+ bool completed, uint64_t last_copied_object, int r) {
+ bufferlist bl;
+ encode(snap_id, bl);
+ encode(completed, bl);
+ encode(last_copied_object, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_test_image_ctx.md_ctx),
+ exec(mock_test_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("mirror_image_snapshot_set_copy_progress"),
+ ContentsEqual(bl), _, _, _))
+ .WillOnce(Return(r));
+ }
+
+ void expect_send(MockCloseImageRequest &mock_close_image_request, int r) {
+ EXPECT_CALL(mock_close_image_request, send())
+ .WillOnce(Invoke([this, &mock_close_image_request, r]() {
+ *mock_close_image_request.image_ctx = nullptr;
+ m_threads->work_queue->queue(mock_close_image_request.on_finish, r);
+ }));
+ }
+
+ void expect_notification(MockThreads& mock_threads,
+ MockReplayerListener& mock_replayer_listener) {
+ EXPECT_CALL(mock_replayer_listener, handle_notification())
+ .WillRepeatedly(Invoke([this]() {
+ std::unique_lock locker{m_lock};
+ ++m_notifications;
+ m_cond.notify_all();
+ }));
+ }
+
+ int wait_for_notification(uint32_t count) {
+ std::unique_lock locker{m_lock};
+ for (uint32_t idx = 0; idx < count; ++idx) {
+ while (m_notifications == 0) {
+ if (m_cond.wait_for(locker, 10s) == std::cv_status::timeout) {
+ return -ETIMEDOUT;
+ }
+ }
+ --m_notifications;
+ }
+ return 0;
+ }
+
+ int init_entry_replayer(MockReplayer& mock_replayer,
+ MockThreads& mock_threads,
+ librbd::MockTestImageCtx& mock_local_image_ctx,
+ librbd::MockTestImageCtx& mock_remote_image_ctx,
+ MockReplayerListener& mock_replayer_listener,
+ MockImageMeta& mock_image_meta,
+ librbd::UpdateWatchCtx** update_watch_ctx) {
+ expect_register_update_watcher(mock_local_image_ctx, update_watch_ctx, 123,
+ 0);
+ expect_register_update_watcher(mock_remote_image_ctx, update_watch_ctx, 234,
+ 0);
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ int r = init_ctx.wait();
+ if (r < 0) {
+ return r;
+ }
+
+ return wait_for_notification(2);
+ }
+
+ int shut_down_entry_replayer(MockReplayer& mock_replayer,
+ MockThreads& mock_threads,
+ librbd::MockTestImageCtx& mock_local_image_ctx,
+ librbd::MockTestImageCtx& mock_remote_image_ctx) {
+ expect_unregister_update_watcher(mock_remote_image_ctx, 234, 0);
+ expect_unregister_update_watcher(mock_local_image_ctx, 123, 0);
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ return shutdown_ctx.wait();
+ }
+
+ librbd::ImageCtx* m_local_image_ctx = nullptr;
+ librbd::ImageCtx* m_remote_image_ctx = nullptr;
+
+ PoolMetaCache m_pool_meta_cache{g_ceph_context};
+
+ ceph::mutex m_lock = ceph::make_mutex(
+ "TestMockImageReplayerSnapshotReplayer");
+ ceph::condition_variable m_cond;
+ uint32_t m_notifications = 0;
+};
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, InitShutDown) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, SyncSnapshot) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ // it should sync two snapshots and skip two (user and mirror w/o matching
+ // peer uuid)
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {""},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {4U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+
+ // init
+ expect_register_update_watcher(mock_local_image_ctx, &update_watch_ctx, 123,
+ 0);
+ expect_register_update_watcher(mock_remote_image_ctx, &update_watch_ctx, 234,
+ 0);
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+ {{1, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 11, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // sync snap4
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {""},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {4U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {5U, librbd::SnapInfo{"snap5", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}
+ }, 0);
+ expect_snapshot_copy(mock_snapshot_copy_request, 1, 4, 11,
+ {{1, 11}, {2, 12}, {4, CEPH_NOSNAP}}, 0);
+ expect_get_image_state(mock_get_image_state_request, 4, 0);
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 4,
+ {{1, 11}, {2, 12}, {4, CEPH_NOSNAP}}, 14,
+ 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ expect_image_copy(mock_image_copy_request, 1, 4, 11, {},
+ {{1, 11}, {2, 12}, {4, CEPH_NOSNAP}}, 0);
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 14, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
+ false, 0);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // prune non-primary snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {14U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 4, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {""},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {4U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}
+ }, 0);
+ expect_prune_non_primary_snapshot(mock_local_image_ctx, 11, 0);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {14U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 4, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {4U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}
+ }, 0);
+
+ // fire init
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(0, init_ctx.wait());
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(4));
+
+ // shut down
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncInitial) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject an incomplete sync snapshot with last_copied_object_number > 0
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, false, 123, {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}}};
+
+ // re-sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 11, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0,
+ librbd::deep_copy::ObjectNumber{123U},
+ {{1, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 11, true, 123, 0);
+ expect_notify_update(mock_local_image_ctx);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(2));
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDelta) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject an incomplete sync snapshot with last_copied_object_number > 0
+ // after a complete snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, false, 123, {{2, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}}};
+
+ // re-sync snap2
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 12, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 1, 2, 11,
+ librbd::deep_copy::ObjectNumber{123U},
+ {{2, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 12, true, 123, 0);
+ expect_notify_update(mock_local_image_ctx);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
+ false, 0);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // prune non-primary snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_prune_non_primary_snapshot(mock_local_image_ctx, 11, 0);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(2));
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedSyncDeltaDemote) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject an incomplete sync snapshot with last_copied_object_number > 0
+ // after a primary demotion snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "local mirror uuid", 11, true, 0,
+ {{11, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, false, 123, {{2, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}}};
+
+ // re-sync snap2
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 12, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 1, 2, 11,
+ librbd::deep_copy::ObjectNumber{123U},
+ {{2, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 12, true, 123, 0);
+ expect_notify_update(mock_local_image_ctx);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
+ false, 0);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(2));
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncInitial) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject an incomplete sync snapshot with last_copied_object_number == 0
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, false, 0, {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}}};
+
+ // re-sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 11, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+ {{1, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 11, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(2));
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDelta) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject an incomplete sync snapshot with last_copied_object_number == 0
+ // after a complete snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, false, 0, {{2, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}}};
+
+ // prune non-primary snap2
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ expect_prune_non_primary_snapshot(mock_local_image_ctx, 12, 0);
+
+ // sync snap2
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 1, 2, 11,
+ {{2, CEPH_NOSNAP}}, 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 2, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 2,
+ {{2, CEPH_NOSNAP}}, 13, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 1, 2, 11, {},
+ {{2, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 13, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
+ false, 0);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // prune non-primary snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {13U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_prune_non_primary_snapshot(mock_local_image_ctx, 11, 0);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {13U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(2));
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, InterruptedPendingSyncDeltaDemote) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject an incomplete sync snapshot with last_copied_object_number == 0
+ // after a primary demotion snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "local mirror uuid", 11, true, 0,
+ {{11, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, false, 0, {{2, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}}};
+
+ // prune non-primary snap2
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ expect_prune_non_primary_snapshot(mock_local_image_ctx, 12, 0);
+
+ // sync snap2
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 1, 2, 11,
+ {{2, CEPH_NOSNAP}}, 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 2, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 2,
+ {{2, CEPH_NOSNAP}}, 13, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 1, 2, 11, {},
+ {{2, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 13, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
+ false, 0);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {13U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 2, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(2));
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteImageDemoted) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject a demotion snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ true, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+ {{1, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 11, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(2));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, LocalImagePromoted) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject a promotion snapshot
+ mock_local_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY,
+ {"remote mirror peer uuid"}, "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, ResyncRequested) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // idle
+ expect_load_image_meta(mock_image_meta, true, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RegisterLocalUpdateWatcherError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayerListener mock_replayer_listener;
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ // init
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ expect_register_update_watcher(mock_local_image_ctx, &update_watch_ctx, 123,
+ -EINVAL);
+
+ // fire init
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-EINVAL, init_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RegisterRemoteUpdateWatcherError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayerListener mock_replayer_listener;
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ // init
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ expect_register_update_watcher(mock_local_image_ctx, &update_watch_ctx, 123,
+ 0);
+ expect_register_update_watcher(mock_remote_image_ctx, &update_watch_ctx, 234,
+ -EINVAL);
+
+ expect_unregister_update_watcher(mock_local_image_ctx, 123, 0);
+
+ // fire init
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(-EINVAL, init_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, UnregisterRemoteUpdateWatcherError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+
+ // shut down
+ expect_unregister_update_watcher(mock_remote_image_ctx, 234, -EINVAL);
+ expect_unregister_update_watcher(mock_local_image_ctx, 123, 0);
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, UnregisterLocalUpdateWatcherError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+
+ // shut down
+ expect_unregister_update_watcher(mock_remote_image_ctx, 234, 0);
+ expect_unregister_update_watcher(mock_local_image_ctx, 123, -EINVAL);
+
+ C_SaferCond shutdown_ctx;
+ mock_replayer.shut_down(&shutdown_ctx);
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, LoadImageMetaError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // sync
+ expect_load_image_meta(mock_image_meta, false, -EINVAL);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RefreshLocalImageError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // sync
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(mock_local_image_ctx, {}, -EINVAL);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RefreshRemoteImageError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // sync
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(mock_remote_image_ctx, {}, -EINVAL);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, CopySnapshotsError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ -EINVAL);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, GetImageStateError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, -EINVAL);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, CreateNonPrimarySnapshotError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, -EINVAL);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, UpdateMirrorImageStateError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, -EIO);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EIO, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RequestSyncError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id,
+ -ECANCELED);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-ECANCELED, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, CopyImageError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP,true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+ {{1, CEPH_NOSNAP}}, -EINVAL);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, UpdateNonPrimarySnapshotError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+ {{1, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 11, true, 0, -EINVAL);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, UnlinkPeerError) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap2
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 1, 2, 11, {{2, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 2, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 2,
+ {{2, CEPH_NOSNAP}}, 12, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 1, 2, 11, {},
+ {{2, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 12, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
+ false, -EINVAL);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EINVAL, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, SplitBrain) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject a primary demote to local image
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED, {}, "", CEPH_NOSNAP,
+ true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // detect split-brain
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EEXIST, mock_replayer.get_error_code());
+ ASSERT_EQ(std::string{"split-brain"}, mock_replayer.get_error_description());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteSnapshotMissingSplitBrain) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject a missing remote start snap (deleted)
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {},
+ "remote mirror uuid", 1, true, 0,
+ {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}}};
+ mock_remote_image_ctx.snap_info = {
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // split-brain due to missing snapshot 1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(-EEXIST, mock_replayer.get_error_code());
+ ASSERT_EQ(std::string{"split-brain"}, mock_replayer.get_error_description());
+
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, RemoteFailover) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject a primary demote to local image
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "local mirror uuid", 12U, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_local_image_ctx.snap_ids = {
+ {{cls::rbd::UserSnapshotNamespace{}, "snap1"}, 11},
+ {{cls::rbd::MirrorSnapshotNamespace{}, "snap2"}, 12}};
+ mock_local_image_ctx.snap_info = {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED, {}, "", CEPH_NOSNAP,
+ true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // attach to promoted remote image
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 2, 3, 12,
+ {{2, 12}, {3, CEPH_NOSNAP}}, 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 3, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 3,
+ {{1, 11}, {2, 12}, {3, CEPH_NOSNAP}}, 13,
+ 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 2, 3, 12, {},
+ {{1, 11}, {2, 12}, {3, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 13, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 2, "remote mirror peer uuid",
+ false, 0);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {12U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED, {}, "", CEPH_NOSNAP,
+ true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {13U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {},
+ "remote mirror uuid", 3, true, 0,
+ {{1, 11}, {2, 12}, {3, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED,
+ {"remote mirror peer uuid"}, "local mirror uuid", 12U, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP, true, 0,
+ {}},
+ 0, {}, 0, 0, {}}}
+ }, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(2));
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, UnlinkRemoteSnapshot) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ // it should attempt to unlink from remote snap1 since we don't need it
+ // anymore
+ mock_local_image_ctx.snap_info = {
+ {14U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 4, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {4U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+
+ // init
+ expect_register_update_watcher(mock_local_image_ctx, &update_watch_ctx, 123,
+ 0);
+ expect_register_update_watcher(mock_remote_image_ctx, &update_watch_ctx, 234,
+ 0);
+
+ // unlink snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ expect_unlink_peer(mock_unlink_peer_request, 1, "remote mirror peer uuid",
+ false, 0);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, true);
+ expect_refresh(
+ mock_remote_image_ctx, {
+ {2U, librbd::SnapInfo{"snap2", cls::rbd::UserSnapshotNamespace{},
+ 0, {}, 0, 0, {}}},
+ {3U, librbd::SnapInfo{"snap3", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {""},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}},
+ {4U, librbd::SnapInfo{"snap4", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}
+ }, 0);
+
+ // fire init
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(0, init_ctx.wait());
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(3));
+
+ // shut down
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, SkipImageSync) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"},
+ "", 0U, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+
+ // init
+ expect_register_update_watcher(mock_local_image_ctx, &update_watch_ctx, 123,
+ 0);
+ expect_register_update_watcher(mock_remote_image_ctx, &update_watch_ctx, 234,
+ 0);
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ expect_apply_image_state(mock_apply_state_request, 0);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 11, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+
+ // idle
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, true);
+ expect_refresh(
+ mock_local_image_ctx, {
+ {11U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "remote mirror uuid",
+ 1, true, 0, {{1, CEPH_NOSNAP}}},
+ 0, {}, 0, 0, {}}},
+ }, 0);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+
+ // fire init
+ C_SaferCond init_ctx;
+ mock_replayer.init(&init_ctx);
+ ASSERT_EQ(0, init_ctx.wait());
+
+ // wait for sync to complete
+ ASSERT_EQ(0, wait_for_notification(3));
+
+ // shut down
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, ImageNameUpdated) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // change the name of the image
+ mock_local_image_ctx.name = "NEW NAME";
+
+ // idle
+ expect_load_image_meta(mock_image_meta, true, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(2));
+ auto image_spec = image_replayer::util::compute_image_spec(m_local_io_ctx,
+ "NEW NAME");
+ ASSERT_EQ(image_spec, mock_replayer.get_image_spec());
+ ASSERT_FALSE(mock_replayer.is_replaying());
+
+ // shut down
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, ApplyImageStatePendingShutdown) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ C_SaferCond shutdown_ctx;
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+ {{1, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ EXPECT_CALL(mock_apply_state_request, send())
+ .WillOnce(Invoke([this, &req=mock_apply_state_request,
+ &replayer=mock_replayer, &ctx=shutdown_ctx]() {
+ // inject a shutdown, to be pended due to STATE_REPLAYING
+ replayer.shut_down(&ctx);
+ m_threads->work_queue->queue(req.on_finish, 0);
+ }));
+ expect_cancel_sync_request(mock_instance_watcher, mock_local_image_ctx.id);
+ expect_mirror_image_snapshot_set_copy_progress(
+ mock_local_image_ctx, 11, true, 0, 0);
+ expect_notify_update(mock_local_image_ctx);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // shutdown should be resumed
+ expect_unregister_update_watcher(mock_remote_image_ctx, 234, 0);
+ expect_unregister_update_watcher(mock_local_image_ctx, 123, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ ASSERT_EQ(0, wait_for_notification(1));
+ ASSERT_FALSE(mock_replayer.is_replaying());
+ ASSERT_EQ(0, mock_replayer.get_error_code());
+
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerSnapshotReplayer, ApplyImageStateErrorPendingShutdown) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ C_SaferCond shutdown_ctx;
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // inject snapshot
+ mock_remote_image_ctx.snap_info = {
+ {1U, librbd::SnapInfo{"snap1", cls::rbd::MirrorSnapshotNamespace{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"remote mirror peer uuid"}, "",
+ CEPH_NOSNAP, true, 0, {}},
+ 0, {}, 0, 0, {}}}};
+
+ // sync snap1
+ expect_load_image_meta(mock_image_meta, false, 0);
+ expect_is_refresh_required(mock_local_image_ctx, false);
+ expect_is_refresh_required(mock_remote_image_ctx, false);
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ expect_snapshot_copy(mock_snapshot_copy_request, 0, 1, 0, {{1, CEPH_NOSNAP}},
+ 0);
+ MockGetImageStateRequest mock_get_image_state_request;
+ expect_get_image_state(mock_get_image_state_request, 1, 0);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_non_primary_request(mock_create_non_primary_request,
+ false, "remote mirror uuid", 1,
+ {{1, CEPH_NOSNAP}}, 11, 0);
+ MockImageStateUpdateRequest mock_image_state_update_request;
+ expect_update_mirror_image_state(mock_image_state_update_request, 0);
+ expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0);
+ MockImageCopyRequest mock_image_copy_request;
+ expect_image_copy(mock_image_copy_request, 0, 1, 0, {},
+ {{1, CEPH_NOSNAP}}, 0);
+ MockApplyImageStateRequest mock_apply_state_request;
+ EXPECT_CALL(mock_apply_state_request, send())
+ .WillOnce(Invoke([this, &req=mock_apply_state_request,
+ &replayer=mock_replayer, &ctx=shutdown_ctx]() {
+ // inject a shutdown, to be pended due to STATE_REPLAYING
+ replayer.shut_down(&ctx);
+ m_threads->work_queue->queue(req.on_finish, -EINVAL);
+ }));
+ expect_cancel_sync_request(mock_instance_watcher, mock_local_image_ctx.id);
+ expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id);
+
+ // shutdown should be resumed
+ expect_unregister_update_watcher(mock_remote_image_ctx, 234, 0);
+ expect_unregister_update_watcher(mock_local_image_ctx, 123, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ ASSERT_EQ(0, shutdown_ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
new file mode 100644
index 000000000..d8d7ed2da
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc
@@ -0,0 +1,1248 @@
+// -*- 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/journal/TypeTraits.h"
+#include "tools/rbd_mirror/BaseRequest.h"
+#include "tools/rbd_mirror/InstanceWatcher.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/BootstrapRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/PrepareLocalImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/PrepareRemoteImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/StateBuilder.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/rbd_mirror/mock/image_sync/MockSyncPointHandler.h"
+#include "test/rbd_mirror/mock/MockBaseRequest.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+class ProgressContext;
+
+template <>
+struct Threads<librbd::MockTestImageCtx> {
+ ceph::mutex &timer_lock;
+ SafeTimer *timer;
+ librbd::asio::ContextWQ *work_queue;
+
+ Threads(Threads<librbd::ImageCtx> *threads)
+ : timer_lock(threads->timer_lock), timer(threads->timer),
+ work_queue(threads->work_queue) {
+ }
+};
+
+template<>
+struct ImageSync<librbd::MockTestImageCtx> {
+ static ImageSync* s_instance;
+ Context *on_finish = nullptr;
+
+ static ImageSync* create(
+ Threads<librbd::MockTestImageCtx>* threads,
+ librbd::MockTestImageCtx *local_image_ctx,
+ librbd::MockTestImageCtx *remote_image_ctx,
+ const std::string &local_mirror_uuid,
+ image_sync::SyncPointHandler* sync_point_handler,
+ InstanceWatcher<librbd::MockTestImageCtx> *instance_watcher,
+ ProgressContext *progress_ctx, Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ ImageSync() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~ImageSync() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD0(get, void());
+ MOCK_METHOD0(put, void());
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD0(cancel, void());
+};
+
+ImageSync<librbd::MockTestImageCtx>*
+ ImageSync<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+template<>
+struct InstanceWatcher<librbd::MockTestImageCtx> {
+};
+
+namespace image_replayer {
+
+template<>
+struct OpenImageRequest<librbd::MockTestImageCtx> {
+ static OpenImageRequest* s_instance;
+ librbd::MockTestImageCtx **image_ctx = nullptr;
+ Context *on_finish = nullptr;
+
+ static OpenImageRequest* create(librados::IoCtx &io_ctx,
+ librbd::MockTestImageCtx **image_ctx,
+ const std::string &image_id,
+ bool read_only, Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->image_ctx = image_ctx;
+ s_instance->on_finish = on_finish;
+ s_instance->construct(io_ctx, image_id);
+ return s_instance;
+ }
+
+ OpenImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~OpenImageRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD2(construct, void(librados::IoCtx &io_ctx,
+ const std::string &image_id));
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct OpenLocalImageRequest<librbd::MockTestImageCtx> {
+ static OpenLocalImageRequest* s_instance;
+ librbd::MockTestImageCtx **image_ctx = nullptr;
+ Context *on_finish = nullptr;
+
+ static OpenLocalImageRequest* create(librados::IoCtx &local_io_ctx,
+ librbd::MockTestImageCtx **local_image_ctx,
+ const std::string &local_image_id,
+ librbd::asio::ContextWQ *work_queue,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->image_ctx = local_image_ctx;
+ s_instance->on_finish = on_finish;
+ s_instance->construct(local_io_ctx, local_image_id);
+ return s_instance;
+ }
+
+ OpenLocalImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~OpenLocalImageRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD2(construct, void(librados::IoCtx &io_ctx,
+ const std::string &image_id));
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct PrepareLocalImageRequest<librbd::MockTestImageCtx> {
+ static PrepareLocalImageRequest* s_instance;
+ std::string *local_image_name = nullptr;
+ StateBuilder<librbd::MockTestImageCtx>** state_builder = nullptr;
+ Context *on_finish = nullptr;
+
+ static PrepareLocalImageRequest* create(librados::IoCtx &,
+ const std::string &global_image_id,
+ std::string *local_image_name,
+ StateBuilder<librbd::MockTestImageCtx>** state_builder,
+ librbd::asio::ContextWQ *work_queue,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->local_image_name = local_image_name;
+ s_instance->state_builder = state_builder;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ PrepareLocalImageRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct PrepareRemoteImageRequest<librbd::MockTestImageCtx> {
+ static PrepareRemoteImageRequest* s_instance;
+ StateBuilder<librbd::MockTestImageCtx>** state_builder = nullptr;
+ Context *on_finish = nullptr;
+
+ static PrepareRemoteImageRequest* create(Threads<librbd::MockTestImageCtx> *threads,
+ librados::IoCtx &,
+ librados::IoCtx &,
+ const std::string &global_image_id,
+ const std::string &local_mirror_uuid,
+ const RemotePoolMeta& remote_pool_meta,
+ ::journal::CacheManagerHandler *cache_manager_handler,
+ StateBuilder<librbd::MockTestImageCtx>** state_builder,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->state_builder = state_builder;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ PrepareRemoteImageRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ static StateBuilder* s_instance;
+
+ image_sync::MockSyncPointHandler mock_sync_point_handler;
+ MockBaseRequest mock_base_request;
+
+ librbd::MockTestImageCtx* local_image_ctx = nullptr;
+ librbd::MockTestImageCtx* remote_image_ctx = nullptr;
+ std::string local_image_id;
+ std::string remote_mirror_uuid;
+ std::string remote_image_id;
+
+ static StateBuilder* create(const std::string&) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ image_sync::MockSyncPointHandler* create_sync_point_handler() {
+ return &mock_sync_point_handler;
+ }
+
+ StateBuilder() {
+ s_instance = this;
+ }
+
+ MOCK_CONST_METHOD0(is_disconnected, bool());
+ MOCK_CONST_METHOD0(is_local_primary, bool());
+ MOCK_CONST_METHOD0(is_remote_primary, bool());
+ MOCK_CONST_METHOD0(is_linked, bool());
+
+ MOCK_CONST_METHOD0(replay_requires_remote_image, bool());
+ MOCK_METHOD1(close_remote_image, void(Context*));
+
+ MOCK_METHOD6(create_local_image_request,
+ BaseRequest*(Threads<librbd::MockTestImageCtx>*,
+ librados::IoCtx&,
+ const std::string&,
+ PoolMetaCache*,
+ ProgressContext*,
+ Context*));
+ MOCK_METHOD5(create_prepare_replay_request,
+ BaseRequest*(const std::string&,
+ ProgressContext*,
+ bool*, bool*, Context*));
+
+ void destroy_sync_point_handler() {
+ }
+ void destroy() {
+ }
+};
+
+OpenImageRequest<librbd::MockTestImageCtx>*
+ OpenImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+OpenLocalImageRequest<librbd::MockTestImageCtx>*
+ OpenLocalImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+PrepareLocalImageRequest<librbd::MockTestImageCtx>*
+ PrepareLocalImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+PrepareRemoteImageRequest<librbd::MockTestImageCtx>*
+ PrepareRemoteImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+StateBuilder<librbd::MockTestImageCtx>*
+ StateBuilder<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/BootstrapRequest.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+MATCHER_P(IsSameIoCtx, io_ctx, "") {
+ return &get_mock_io_ctx(arg) == &get_mock_io_ctx(*io_ctx);
+}
+
+class TestMockImageReplayerBootstrapRequest : public TestMockFixture {
+public:
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef BootstrapRequest<librbd::MockTestImageCtx> MockBootstrapRequest;
+ typedef ImageSync<librbd::MockTestImageCtx> MockImageSync;
+ typedef InstanceWatcher<librbd::MockTestImageCtx> MockInstanceWatcher;
+ typedef OpenImageRequest<librbd::MockTestImageCtx> MockOpenImageRequest;
+ typedef OpenLocalImageRequest<librbd::MockTestImageCtx> MockOpenLocalImageRequest;
+ typedef PrepareLocalImageRequest<librbd::MockTestImageCtx> MockPrepareLocalImageRequest;
+ typedef PrepareRemoteImageRequest<librbd::MockTestImageCtx> MockPrepareRemoteImageRequest;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+ typedef std::list<cls::journal::Tag> Tags;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::RBD rbd;
+ 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));
+
+ 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_send(MockPrepareLocalImageRequest &mock_request,
+ MockStateBuilder& mock_state_builder,
+ const std::string& local_image_id,
+ const std::string& local_image_name, int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(Invoke([&mock_request, &mock_state_builder, local_image_id,
+ local_image_name, r]() {
+ if (r == 0) {
+ *mock_request.state_builder = &mock_state_builder;
+ mock_state_builder.local_image_id = local_image_id;
+ *mock_request.local_image_name = local_image_name;
+ }
+ mock_request.on_finish->complete(r);
+ }));
+ }
+
+ void expect_send(MockPrepareRemoteImageRequest& mock_request,
+ MockStateBuilder& mock_state_builder,
+ const std::string& remote_mirror_uuid,
+ const std::string& remote_image_id,
+ int r) {
+ EXPECT_CALL(mock_request, send())
+ .WillOnce(Invoke([&mock_request, &mock_state_builder, remote_mirror_uuid,
+ remote_image_id, r]() {
+ if (r >= 0) {
+ *mock_request.state_builder = &mock_state_builder;
+ mock_state_builder.remote_image_id = remote_image_id;
+ }
+
+ mock_state_builder.remote_mirror_uuid = remote_mirror_uuid;
+ mock_request.on_finish->complete(r);
+ }));
+ }
+
+ void expect_is_local_primary(MockStateBuilder& mock_state_builder,
+ bool is_primary) {
+ EXPECT_CALL(mock_state_builder, is_local_primary())
+ .WillOnce(Return(is_primary));
+ }
+
+ void expect_is_remote_primary(MockStateBuilder& mock_state_builder,
+ bool is_primary) {
+ EXPECT_CALL(mock_state_builder, is_remote_primary())
+ .WillOnce(Return(is_primary));
+ }
+
+ void expect_is_linked(MockStateBuilder& mock_state_builder, bool is_linked) {
+ EXPECT_CALL(mock_state_builder, is_linked())
+ .WillOnce(Return(is_linked));
+ }
+
+ void expect_is_disconnected(MockStateBuilder& mock_state_builder,
+ bool is_disconnected) {
+ EXPECT_CALL(mock_state_builder, is_disconnected())
+ .WillOnce(Return(is_disconnected));
+ }
+
+ void expect_replay_requires_remote_image(MockStateBuilder& mock_state_builder,
+ bool requires_image) {
+ EXPECT_CALL(mock_state_builder, replay_requires_remote_image())
+ .WillOnce(Return(requires_image));
+ }
+
+ void expect_open_image(MockOpenImageRequest &mock_open_image_request,
+ librados::IoCtx &io_ctx, const std::string &image_id,
+ librbd::MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(mock_open_image_request,
+ construct(IsSameIoCtx(&io_ctx), image_id));
+ EXPECT_CALL(mock_open_image_request, send())
+ .WillOnce(Invoke([this, &mock_open_image_request, &mock_image_ctx, r]() {
+ *mock_open_image_request.image_ctx = &mock_image_ctx;
+ m_threads->work_queue->queue(mock_open_image_request.on_finish, r);
+ }));
+ }
+
+ void expect_open_local_image(MockOpenLocalImageRequest &mock_open_local_image_request,
+ librados::IoCtx &io_ctx, const std::string &image_id,
+ librbd::MockTestImageCtx *mock_image_ctx, int r) {
+ EXPECT_CALL(mock_open_local_image_request,
+ construct(IsSameIoCtx(&io_ctx), image_id));
+ EXPECT_CALL(mock_open_local_image_request, send())
+ .WillOnce(Invoke([this, &mock_open_local_image_request, mock_image_ctx, r]() {
+ if (r >= 0) {
+ *mock_open_local_image_request.image_ctx = mock_image_ctx;
+ }
+ m_threads->work_queue->queue(mock_open_local_image_request.on_finish,
+ r);
+ }));
+ }
+
+ void expect_close_remote_image(
+ MockStateBuilder& mock_state_builder, int r) {
+ EXPECT_CALL(mock_state_builder, close_remote_image(_))
+ .WillOnce(Invoke([&mock_state_builder, r]
+ (Context* on_finish) {
+ mock_state_builder.remote_image_ctx = nullptr;
+ on_finish->complete(r);
+ }));
+ }
+
+ void expect_create_local_image(MockStateBuilder& mock_state_builder,
+ const std::string& local_image_id, int r) {
+ EXPECT_CALL(mock_state_builder,
+ create_local_image_request(_, _, _, _, _, _))
+ .WillOnce(WithArg<5>(
+ Invoke([&mock_state_builder, local_image_id, r](Context* ctx) {
+ if (r >= 0) {
+ mock_state_builder.local_image_id = local_image_id;
+ }
+ mock_state_builder.mock_base_request.on_finish = ctx;
+ return &mock_state_builder.mock_base_request;
+ })));
+ EXPECT_CALL(mock_state_builder.mock_base_request, send())
+ .WillOnce(Invoke([this, &mock_state_builder, r]() {
+ m_threads->work_queue->queue(
+ mock_state_builder.mock_base_request.on_finish, r);
+ }));
+ }
+
+ void expect_prepare_replay(MockStateBuilder& mock_state_builder,
+ bool resync_requested, bool syncing, int r) {
+ EXPECT_CALL(mock_state_builder,
+ create_prepare_replay_request(_, _, _, _, _))
+ .WillOnce(WithArgs<2, 3, 4>(
+ Invoke([&mock_state_builder, resync_requested, syncing, r]
+ (bool* resync, bool* sync, Context* ctx) {
+ if (r >= 0) {
+ *resync = resync_requested;
+ *sync = syncing;
+ }
+ mock_state_builder.mock_base_request.on_finish = ctx;
+ return &mock_state_builder.mock_base_request;
+ })));
+ EXPECT_CALL(mock_state_builder.mock_base_request, send())
+ .WillOnce(Invoke([this, &mock_state_builder, r]() {
+ m_threads->work_queue->queue(
+ mock_state_builder.mock_base_request.on_finish, r);
+ }));
+ }
+
+ void expect_image_sync(MockImageSync &mock_image_sync, int r) {
+ EXPECT_CALL(mock_image_sync, get());
+ EXPECT_CALL(mock_image_sync, send())
+ .WillOnce(Invoke([this, &mock_image_sync, r]() {
+ m_threads->work_queue->queue(mock_image_sync.on_finish, r);
+ }));
+ EXPECT_CALL(mock_image_sync, put());
+ }
+
+ MockBootstrapRequest *create_request(MockThreads* mock_threads,
+ MockInstanceWatcher *mock_instance_watcher,
+ const std::string &global_image_id,
+ const std::string &local_mirror_uuid,
+ Context *on_finish) {
+ return new MockBootstrapRequest(mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ mock_instance_watcher,
+ global_image_id,
+ local_mirror_uuid,
+ {"remote mirror uuid",
+ "remote mirror peer uuid"},
+ nullptr, nullptr, nullptr,
+ &m_mock_state_builder,
+ &m_do_resync, on_finish);
+ }
+
+ librbd::ImageCtx *m_remote_image_ctx;
+ librbd::ImageCtx *m_local_image_ctx = nullptr;
+
+ MockStateBuilder* m_mock_state_builder = nullptr;
+ bool m_do_resync = false;
+};
+
+TEST_F(TestMockImageReplayerBootstrapRequest, Success) {
+ InSequence seq;
+
+ // prepare local image
+ MockStateBuilder mock_state_builder;
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, false, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareRemoteImageNotPrimaryLocalDNE) {
+ InSequence seq;
+
+ // prepare local image
+ MockStateBuilder mock_state_builder;
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, -ENOENT);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, false);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-EREMOTEIO, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareRemoteImageNotPrimaryLocalUnlinked) {
+ InSequence seq;
+
+ // prepare local image
+ MockStateBuilder mock_state_builder;
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, false);
+ expect_is_linked(mock_state_builder, false);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-EREMOTEIO, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareRemoteImageNotPrimaryLocalLinked) {
+ InSequence seq;
+
+ // prepare local image
+ MockStateBuilder mock_state_builder;
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, false);
+ expect_is_linked(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, false, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareRemoteImageDNELocalLinked) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, -ENOENT);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_linked(mock_state_builder, true);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-ENOLINK, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareRemoteImageDNELocalLinkedCanceled) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, -ENOENT);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_linked(mock_state_builder, true);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->cancel();
+ request->send();
+ ASSERT_EQ(-ENOLINK, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, OpenLocalImageError) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx,
+ -EINVAL);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, OpenLocalImageDNE) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx,
+ -ENOENT);
+
+ // create local image
+ expect_create_local_image(mock_state_builder, "local image id", 0);
+
+ // re-open the local image
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ "local image id", &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, false, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, OpenLocalImagePrimary) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx,
+ -EREMOTEIO);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-EREMOTEIO, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, CreateLocalImageError) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder, "", "",
+ -ENOENT);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // create local image
+ expect_create_local_image(mock_state_builder, "local image id", -EINVAL);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareReplayError) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, false, -EINVAL);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareReplayResyncRequested) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, true, false, 0);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_TRUE(m_do_resync);
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareReplaySyncing) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, true, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // image sync
+ MockImageSync mock_image_sync;
+ expect_image_sync(mock_image_sync, 0);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, PrepareReplayDisconnected) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, false, 0);
+ expect_is_disconnected(mock_state_builder, true);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, ImageSyncError) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, true, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // image sync
+ MockImageSync mock_image_sync;
+ expect_image_sync(mock_image_sync, -EINVAL);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, ImageSyncCanceled) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, true, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->cancel();
+ request->send();
+ ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, CloseRemoteImageError) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, false, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // attempt to close remote image
+ expect_replay_requires_remote_image(mock_state_builder, false);
+ expect_close_remote_image(mock_state_builder, -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerBootstrapRequest, ReplayRequiresRemoteImage) {
+ InSequence seq;
+
+ // prepare local image
+ MockPrepareLocalImageRequest mock_prepare_local_image_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_prepare_local_image_request, mock_state_builder,
+ m_local_image_ctx->id, m_local_image_ctx->name, 0);
+
+ // prepare remote image
+ MockPrepareRemoteImageRequest mock_prepare_remote_image_request;
+ expect_send(mock_prepare_remote_image_request, mock_state_builder,
+ "remote mirror uuid", m_remote_image_ctx->id, 0);
+ expect_is_local_primary(mock_state_builder, false);
+ expect_is_remote_primary(mock_state_builder, true);
+
+ // open the remote image
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ mock_remote_image_ctx.id, mock_remote_image_ctx, 0);
+
+ // open the local image
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ MockOpenLocalImageRequest mock_open_local_image_request;
+ expect_open_local_image(mock_open_local_image_request, m_local_io_ctx,
+ mock_local_image_ctx.id, &mock_local_image_ctx, 0);
+
+ // prepare replay
+ expect_prepare_replay(mock_state_builder, false, false, 0);
+ expect_is_disconnected(mock_state_builder, false);
+
+ // remote image is left open
+ expect_replay_requires_remote_image(mock_state_builder, true);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockInstanceWatcher mock_instance_watcher;
+ MockBootstrapRequest *request = create_request(
+ &mock_threads, &mock_instance_watcher, "global image id",
+ "local mirror uuid", &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc
new file mode 100644
index 000000000..ed2cf8f96
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc
@@ -0,0 +1,614 @@
+// -*- 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 "include/rbd/librbd.hpp"
+#include "librbd/ImageState.h"
+#include "librbd/Operations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "tools/rbd_mirror/PoolMetaCache.h"
+#include "tools/rbd_mirror/image_replayer/CreateImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/CloseImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.h"
+#include "librbd/image/CreateRequest.h"
+#include "librbd/image/CloneRequest.h"
+#include "tools/rbd_mirror/Threads.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 CreateRequest<librbd::MockTestImageCtx> {
+ static CreateRequest *s_instance;
+ Context *on_finish = nullptr;
+
+ static CreateRequest *create(const ConfigProxy& config, IoCtx &ioctx,
+ const std::string &imgname,
+ const std::string &imageid, uint64_t size,
+ const librbd::ImageOptions &image_options,
+ bool skip_mirror_enable,
+ cls::rbd::MirrorImageMode mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ MockContextWQ *op_work_queue,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ EXPECT_FALSE(non_primary_global_image_id.empty());
+ EXPECT_FALSE(primary_mirror_uuid.empty());
+ EXPECT_FALSE(skip_mirror_enable);
+ s_instance->on_finish = on_finish;
+ s_instance->construct(ioctx);
+ return s_instance;
+ }
+
+ CreateRequest() {
+ s_instance = this;
+ }
+
+ ~CreateRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD1(construct, void(librados::IoCtx &ioctx));
+};
+
+CreateRequest<librbd::MockTestImageCtx>*
+ CreateRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+template<>
+struct CloneRequest<librbd::MockTestImageCtx> {
+ static CloneRequest *s_instance;
+ Context *on_finish = nullptr;
+
+ static CloneRequest *create(ConfigProxy& config, IoCtx &p_ioctx,
+ const std::string &p_id,
+ const std::string &p_snap_name,
+ const cls::rbd::SnapshotNamespace& snap_ns,
+ uint64_t p_snap_id,
+ IoCtx &c_ioctx, const std::string &c_name,
+ const std::string &c_id, ImageOptions c_options,
+ cls::rbd::MirrorImageMode mode,
+ const std::string &non_primary_global_image_id,
+ const std::string &primary_mirror_uuid,
+ MockContextWQ *op_work_queue,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ s_instance->construct();
+ return s_instance;
+ }
+
+ CloneRequest() {
+ s_instance = this;
+ }
+
+ ~CloneRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD0(construct, void());
+};
+
+CloneRequest<librbd::MockTestImageCtx>*
+ CloneRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+template <>
+struct Threads<librbd::MockTestImageCtx> {
+ ceph::mutex &timer_lock;
+ SafeTimer *timer;
+ librbd::asio::ContextWQ *work_queue;
+
+ Threads(Threads<librbd::ImageCtx> *threads)
+ : timer_lock(threads->timer_lock), timer(threads->timer),
+ work_queue(threads->work_queue) {
+ }
+};
+
+namespace image_replayer {
+
+template<>
+struct CloseImageRequest<librbd::MockTestImageCtx> {
+ static CloseImageRequest* s_instance;
+ Context *on_finish = nullptr;
+
+ static CloseImageRequest* create(librbd::MockTestImageCtx **image_ctx,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->construct(*image_ctx);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ CloseImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~CloseImageRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD1(construct, void(librbd::MockTestImageCtx *image_ctx));
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct OpenImageRequest<librbd::MockTestImageCtx> {
+ static OpenImageRequest* s_instance;
+ librbd::MockTestImageCtx **image_ctx = nullptr;
+ Context *on_finish = nullptr;
+
+ static OpenImageRequest* create(librados::IoCtx &io_ctx,
+ librbd::MockTestImageCtx **image_ctx,
+ const std::string &image_id,
+ bool read_only, Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->image_ctx = image_ctx;
+ s_instance->on_finish = on_finish;
+ s_instance->construct(io_ctx, image_id);
+ return s_instance;
+ }
+
+ OpenImageRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~OpenImageRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD2(construct, void(librados::IoCtx &io_ctx,
+ const std::string &image_id));
+ MOCK_METHOD0(send, void());
+};
+
+CloseImageRequest<librbd::MockTestImageCtx>*
+ CloseImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+OpenImageRequest<librbd::MockTestImageCtx>*
+ OpenImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/CreateImageRequest.cc"
+template class rbd::mirror::image_replayer::CreateImageRequest<librbd::MockTestImageCtx>;
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+MATCHER_P(IsSameIoCtx, io_ctx, "") {
+ return &get_mock_io_ctx(arg) == &get_mock_io_ctx(*io_ctx);
+}
+
+class TestMockImageReplayerCreateImageRequest : public TestMockFixture {
+public:
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef librbd::image::CreateRequest<librbd::MockTestImageCtx> MockCreateRequest;
+ typedef librbd::image::CloneRequest<librbd::MockTestImageCtx> MockCloneRequest;
+ typedef CreateImageRequest<librbd::MockTestImageCtx> MockCreateImageRequest;
+ typedef OpenImageRequest<librbd::MockTestImageCtx> MockOpenImageRequest;
+ typedef CloseImageRequest<librbd::MockTestImageCtx> MockCloseImageRequest;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ librbd::RBD rbd;
+ 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));
+ }
+
+ void snap_create(librbd::ImageCtx *image_ctx, const std::string &snap_name) {
+ librbd::NoOpProgressContext prog_ctx;
+ ASSERT_EQ(0, image_ctx->operations->snap_create(cls::rbd::UserSnapshotNamespace(),
+ snap_name, 0, prog_ctx));
+ ASSERT_EQ(0, image_ctx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(),
+ snap_name));
+ ASSERT_EQ(0, image_ctx->state->refresh());
+ }
+
+ int clone_image(librbd::ImageCtx *parent_image_ctx,
+ const std::string &snap_name, const std::string &clone_name) {
+ snap_create(parent_image_ctx, snap_name);
+
+ int order = 0;
+ return librbd::clone(m_remote_io_ctx, parent_image_ctx->name.c_str(),
+ snap_name.c_str(), m_remote_io_ctx,
+ clone_name.c_str(), parent_image_ctx->features,
+ &order, 0, 0);
+ }
+
+ void expect_create_image(MockCreateRequest &mock_create_request,
+ librados::IoCtx &ioctx, int r) {
+ EXPECT_CALL(mock_create_request, construct(IsSameIoCtx(&ioctx)));
+ EXPECT_CALL(mock_create_request, send())
+ .WillOnce(Invoke([this, &mock_create_request, r]() {
+ m_threads->work_queue->queue(mock_create_request.on_finish, r);
+ }));
+ }
+
+ void expect_ioctx_create(librados::IoCtx &io_ctx) {
+ librados::MockTestMemIoCtxImpl &io_ctx_impl = get_mock_io_ctx(io_ctx);
+ EXPECT_CALL(*get_mock_io_ctx(io_ctx).get_mock_rados_client(), create_ioctx(_, _))
+ .WillOnce(DoAll(GetReference(&io_ctx_impl),
+ Return(&get_mock_io_ctx(io_ctx))));
+ }
+
+ void expect_get_parent_global_image_id(librados::IoCtx &io_ctx,
+ const std::string &global_id, int r) {
+ cls::rbd::MirrorImage mirror_image;
+ mirror_image.global_image_id = global_id;
+
+ bufferlist bl;
+ encode(mirror_image, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(io_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_get"),
+ _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) {
+ *out_bl = bl;
+ })),
+ Return(r)));
+ }
+
+ void expect_mirror_image_get_image_id(librados::IoCtx &io_ctx,
+ const std::string &image_id, int r) {
+ bufferlist bl;
+ encode(image_id, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(io_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"),
+ StrEq("mirror_image_get_image_id"), _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) {
+ *out_bl = bl;
+ })),
+ Return(r)));
+ }
+
+ void expect_open_image(MockOpenImageRequest &mock_open_image_request,
+ librados::IoCtx &io_ctx, const std::string &image_id,
+ librbd::MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(mock_open_image_request, construct(IsSameIoCtx(&io_ctx), image_id));
+ EXPECT_CALL(mock_open_image_request, send())
+ .WillOnce(Invoke([this, &mock_open_image_request, &mock_image_ctx, r]() {
+ *mock_open_image_request.image_ctx = &mock_image_ctx;
+ m_threads->work_queue->queue(mock_open_image_request.on_finish, r);
+ }));
+ }
+
+ void expect_test_op_features(librbd::MockTestImageCtx& mock_image_ctx,
+ bool enabled) {
+ EXPECT_CALL(mock_image_ctx,
+ test_op_features(RBD_OPERATION_FEATURE_CLONE_CHILD))
+ .WillOnce(Return(enabled));
+ }
+
+ void expect_clone_image(MockCloneRequest &mock_clone_request,
+ int r) {
+ EXPECT_CALL(mock_clone_request, construct());
+ EXPECT_CALL(mock_clone_request, send())
+ .WillOnce(Invoke([this, &mock_clone_request, r]() {
+ m_threads->work_queue->queue(mock_clone_request.on_finish, r);
+ }));
+ }
+
+ void expect_close_image(MockCloseImageRequest &mock_close_image_request,
+ librbd::MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(mock_close_image_request, construct(&mock_image_ctx));
+ EXPECT_CALL(mock_close_image_request, send())
+ .WillOnce(Invoke([this, &mock_close_image_request, r]() {
+ m_threads->work_queue->queue(mock_close_image_request.on_finish, r);
+ }));
+ }
+
+ MockCreateImageRequest *create_request(MockThreads* mock_threads,
+ const std::string &global_image_id,
+ const std::string &remote_mirror_uuid,
+ const std::string &local_image_name,
+ const std::string &local_image_id,
+ librbd::MockTestImageCtx &mock_remote_image_ctx,
+ Context *on_finish) {
+ return new MockCreateImageRequest(mock_threads, m_local_io_ctx,
+ global_image_id, remote_mirror_uuid,
+ local_image_name, local_image_id,
+ &mock_remote_image_ctx,
+ &m_pool_meta_cache,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ on_finish);
+ }
+
+ PoolMetaCache m_pool_meta_cache{g_ceph_context};
+ librbd::ImageCtx *m_remote_image_ctx;
+};
+
+TEST_F(TestMockImageReplayerCreateImageRequest, Create) {
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockCreateRequest mock_create_request;
+
+ InSequence seq;
+ expect_create_image(mock_create_request, m_local_io_ctx, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_image_ctx, &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CreateError) {
+ librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ MockCreateRequest mock_create_request;
+
+ InSequence seq;
+ expect_create_image(mock_create_request, m_local_io_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_image_ctx, &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneGetGlobalImageIdError) {
+ std::string clone_image_name = get_temp_image_name();
+ ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+ librbd::ImageCtx *remote_clone_image_ctx;
+ ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+ &remote_clone_image_ctx));
+
+ librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+
+ InSequence seq;
+ expect_ioctx_create(m_remote_io_ctx);
+ expect_ioctx_create(m_local_io_ctx);
+ expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", -ENOENT);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_clone_image_ctx,
+ &ctx);
+ request->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneGetLocalParentImageIdError) {
+ std::string clone_image_name = get_temp_image_name();
+ ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+ librbd::ImageCtx *remote_clone_image_ctx;
+ ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+ &remote_clone_image_ctx));
+
+ librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+
+ InSequence seq;
+ expect_ioctx_create(m_remote_io_ctx);
+ expect_ioctx_create(m_local_io_ctx);
+ expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+ expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", -ENOENT);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_clone_image_ctx,
+ &ctx);
+ request->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneOpenRemoteParentError) {
+ std::string clone_image_name = get_temp_image_name();
+ ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+ librbd::ImageCtx *remote_clone_image_ctx;
+ ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+ &remote_clone_image_ctx));
+
+ librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+
+ InSequence seq;
+ expect_ioctx_create(m_remote_io_ctx);
+ expect_ioctx_create(m_local_io_ctx);
+ expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+ expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ m_remote_image_ctx->id, mock_remote_parent_image_ctx,
+ -ENOENT);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_clone_image_ctx,
+ &ctx);
+ request->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneParentImageSyncing) {
+ librbd::RBD rbd;
+ librbd::ImageCtx *local_image_ctx;
+ 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, &local_image_ctx));
+ snap_create(local_image_ctx, "snap");
+ snap_create(m_remote_image_ctx, ".rbd-mirror.local parent uuid.1234");
+
+ std::string clone_image_name = get_temp_image_name();
+ ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+ librbd::ImageCtx *remote_clone_image_ctx;
+ ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+ &remote_clone_image_ctx));
+
+ m_pool_meta_cache.set_local_pool_meta(
+ m_local_io_ctx.get_id(), {"local parent uuid"});
+
+ librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+ MockOpenImageRequest mock_open_image_request;
+ MockCloseImageRequest mock_close_image_request;
+
+ InSequence seq;
+ expect_ioctx_create(m_remote_io_ctx);
+ expect_ioctx_create(m_local_io_ctx);
+ expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+ expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+ expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_clone_image_ctx,
+ &ctx);
+ request->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneError) {
+ librbd::RBD rbd;
+ librbd::ImageCtx *local_image_ctx;
+ 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, &local_image_ctx));
+ snap_create(local_image_ctx, "snap");
+
+ std::string clone_image_name = get_temp_image_name();
+ ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+ librbd::ImageCtx *remote_clone_image_ctx;
+ ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+ &remote_clone_image_ctx));
+
+ m_pool_meta_cache.set_local_pool_meta(
+ m_local_io_ctx.get_id(), {"local parent uuid"});
+
+ librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+ MockCloneRequest mock_clone_request;
+ MockOpenImageRequest mock_open_image_request;
+ MockCloseImageRequest mock_close_image_request;
+
+ InSequence seq;
+ expect_ioctx_create(m_remote_io_ctx);
+ expect_ioctx_create(m_local_io_ctx);
+ expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+ expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+ expect_test_op_features(mock_remote_clone_image_ctx, false);
+ expect_clone_image(mock_clone_request, -EINVAL);
+ expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx, 0);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_clone_image_ctx,
+ &ctx);
+ request->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerCreateImageRequest, CloneRemoteParentCloseError) {
+ librbd::RBD rbd;
+ librbd::ImageCtx *local_image_ctx;
+ 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, &local_image_ctx));
+ snap_create(local_image_ctx, "snap");
+
+ std::string clone_image_name = get_temp_image_name();
+ ASSERT_EQ(0, clone_image(m_remote_image_ctx, "snap", clone_image_name));
+
+ librbd::ImageCtx *remote_clone_image_ctx;
+ ASSERT_EQ(0, open_image(m_remote_io_ctx, clone_image_name,
+ &remote_clone_image_ctx));
+
+ m_pool_meta_cache.set_local_pool_meta(
+ m_local_io_ctx.get_id(), {"local parent uuid"});
+
+ librbd::MockTestImageCtx mock_remote_parent_image_ctx(*m_remote_image_ctx);
+ librbd::MockTestImageCtx mock_remote_clone_image_ctx(*remote_clone_image_ctx);
+ MockCloneRequest mock_clone_request;
+ MockOpenImageRequest mock_open_image_request;
+ MockCloseImageRequest mock_close_image_request;
+
+ InSequence seq;
+ expect_ioctx_create(m_remote_io_ctx);
+ expect_ioctx_create(m_local_io_ctx);
+ expect_get_parent_global_image_id(m_remote_io_ctx, "global uuid", 0);
+ expect_mirror_image_get_image_id(m_local_io_ctx, "local parent id", 0);
+
+ expect_open_image(mock_open_image_request, m_remote_io_ctx,
+ m_remote_image_ctx->id, mock_remote_parent_image_ctx, 0);
+ expect_test_op_features(mock_remote_clone_image_ctx, false);
+ expect_clone_image(mock_clone_request, 0);
+ expect_close_image(mock_close_image_request, mock_remote_parent_image_ctx,
+ -EINVAL);
+
+ C_SaferCond ctx;
+ MockThreads mock_threads(m_threads);
+ MockCreateImageRequest *request = create_request(&mock_threads, "global uuid",
+ "remote uuid", "image name",
+ "101241a7c4c9",
+ mock_remote_clone_image_ctx,
+ &ctx);
+ request->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_GetMirrorImageIdRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_GetMirrorImageIdRequest.cc
new file mode 100644
index 000000000..4a238d282
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/test_mock_GetMirrorImageIdRequest.cc
@@ -0,0 +1,107 @@
+// -*- 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 "cls/rbd/cls_rbd_types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/image_replayer/GetMirrorImageIdRequest.h"
+#include "test/journal/mock/MockJournaler.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockJournal.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/GetMirrorImageIdRequest.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+class TestMockImageReplayerGetMirrorImageIdRequest : public TestMockFixture {
+public:
+ typedef GetMirrorImageIdRequest<librbd::MockTestImageCtx> MockGetMirrorImageIdRequest;
+
+ void expect_mirror_image_get_image_id(librados::IoCtx &io_ctx,
+ const std::string &image_id, int r) {
+ bufferlist bl;
+ encode(image_id, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(io_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"),
+ StrEq("mirror_image_get_image_id"), _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) {
+ *out_bl = bl;
+ })),
+ Return(r)));
+ }
+
+};
+
+TEST_F(TestMockImageReplayerGetMirrorImageIdRequest, Success) {
+ InSequence seq;
+ expect_mirror_image_get_image_id(m_local_io_ctx, "image id", 0);
+
+ std::string image_id;
+ C_SaferCond ctx;
+ auto req = MockGetMirrorImageIdRequest::create(m_local_io_ctx,
+ "global image id",
+ &image_id, &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_EQ(std::string("image id"), image_id);
+}
+
+TEST_F(TestMockImageReplayerGetMirrorImageIdRequest, MirrorImageIdDNE) {
+ InSequence seq;
+ expect_mirror_image_get_image_id(m_local_io_ctx, "", -ENOENT);
+
+ std::string image_id;
+ C_SaferCond ctx;
+ auto req = MockGetMirrorImageIdRequest::create(m_local_io_ctx,
+ "global image id",
+ &image_id, &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerGetMirrorImageIdRequest, MirrorImageIdError) {
+ InSequence seq;
+ expect_mirror_image_get_image_id(m_local_io_ctx, "", -EINVAL);
+
+ std::string image_id;
+ C_SaferCond ctx;
+ auto req = MockGetMirrorImageIdRequest::create(m_local_io_ctx,
+ "global image id",
+ &image_id, &ctx);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_PrepareLocalImageRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_PrepareLocalImageRequest.cc
new file mode 100644
index 000000000..54c3b24ef
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/test_mock_PrepareLocalImageRequest.cc
@@ -0,0 +1,505 @@
+// -*- 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 "cls/rbd/cls_rbd_types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "librbd/mirror/GetInfoRequest.h"
+#include "tools/rbd_mirror/ImageDeleter.h"
+#include "tools/rbd_mirror/image_replayer/GetMirrorImageIdRequest.h"
+#include "tools/rbd_mirror/image_replayer/PrepareLocalImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/StateBuilder.h"
+#include "tools/rbd_mirror/image_replayer/journal/StateBuilder.h"
+#include "tools/rbd_mirror/image_replayer/snapshot/StateBuilder.h"
+#include "test/journal/mock/MockJournaler.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockJournal.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace mirror {
+
+template<>
+struct GetInfoRequest<librbd::MockTestImageCtx> {
+ static GetInfoRequest* s_instance;
+ cls::rbd::MirrorImage *mirror_image;
+ PromotionState *promotion_state;
+ std::string *primary_mirror_uuid;
+ Context *on_finish = nullptr;
+
+ static GetInfoRequest* create(librados::IoCtx& io_ctx,
+ librbd::asio::ContextWQ* context_wq,
+ const std::string& image_id,
+ cls::rbd::MirrorImage *mirror_image,
+ PromotionState *promotion_state,
+ std::string* primary_mirror_uuid,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->mirror_image = mirror_image;
+ s_instance->promotion_state = promotion_state;
+ s_instance->primary_mirror_uuid = primary_mirror_uuid;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ GetInfoRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~GetInfoRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+GetInfoRequest<librbd::MockTestImageCtx>* GetInfoRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace mirror
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+template <>
+struct ImageDeleter<librbd::MockTestImageCtx> {
+ static ImageDeleter* s_instance;
+
+ static void trash_move(librados::IoCtx& local_io_ctx,
+ const std::string& global_image_id, bool resync,
+ librbd::asio::ContextWQ* work_queue,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->trash_move(global_image_id, resync, on_finish);
+ }
+
+ MOCK_METHOD3(trash_move, void(const std::string&, bool, Context*));
+
+ ImageDeleter() {
+ s_instance = this;
+ }
+};
+
+ImageDeleter<librbd::MockTestImageCtx>* ImageDeleter<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace image_replayer {
+
+template <>
+struct GetMirrorImageIdRequest<librbd::MockTestImageCtx> {
+ static GetMirrorImageIdRequest* s_instance;
+ std::string* image_id = nullptr;
+ Context* on_finish = nullptr;
+
+ static GetMirrorImageIdRequest* create(librados::IoCtx& io_ctx,
+ const std::string& global_image_id,
+ std::string* image_id,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->image_id = image_id;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ GetMirrorImageIdRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ virtual ~StateBuilder() {}
+
+ std::string local_image_id;
+ librbd::mirror::PromotionState local_promotion_state;
+};
+
+GetMirrorImageIdRequest<librbd::MockTestImageCtx>* GetMirrorImageIdRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace journal {
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx>
+ : public image_replayer::StateBuilder<librbd::MockTestImageCtx> {
+ static StateBuilder* s_instance;
+
+ cls::rbd::MirrorImageMode mirror_image_mode =
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL;
+
+ std::string local_primary_mirror_uuid;
+
+ static StateBuilder* create(const std::string&) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ StateBuilder() {
+ s_instance = this;
+ }
+};
+
+StateBuilder<librbd::MockTestImageCtx>* StateBuilder<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace journal
+
+namespace snapshot {
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx>
+ : public image_replayer::StateBuilder<librbd::MockTestImageCtx> {
+ static StateBuilder* s_instance;
+
+ cls::rbd::MirrorImageMode mirror_image_mode =
+ cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT;
+
+ static StateBuilder* create(const std::string&) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ StateBuilder() {
+ s_instance = this;
+ }
+};
+
+StateBuilder<librbd::MockTestImageCtx>* StateBuilder<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/PrepareLocalImageRequest.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+class TestMockImageReplayerPrepareLocalImageRequest : public TestMockFixture {
+public:
+ typedef ImageDeleter<librbd::MockTestImageCtx> MockImageDeleter;
+ typedef PrepareLocalImageRequest<librbd::MockTestImageCtx> MockPrepareLocalImageRequest;
+ typedef GetMirrorImageIdRequest<librbd::MockTestImageCtx> MockGetMirrorImageIdRequest;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+ typedef journal::StateBuilder<librbd::MockTestImageCtx> MockJournalStateBuilder;
+ typedef snapshot::StateBuilder<librbd::MockTestImageCtx> MockSnapshotStateBuilder;
+ typedef librbd::mirror::GetInfoRequest<librbd::MockTestImageCtx> MockGetMirrorInfoRequest;
+
+ void expect_get_mirror_image_id(MockGetMirrorImageIdRequest& mock_get_mirror_image_id_request,
+ const std::string& image_id, int r) {
+ EXPECT_CALL(mock_get_mirror_image_id_request, send())
+ .WillOnce(Invoke([&mock_get_mirror_image_id_request, image_id, r]() {
+ *mock_get_mirror_image_id_request.image_id = image_id;
+ mock_get_mirror_image_id_request.on_finish->complete(r);
+ }));
+ }
+
+ void expect_dir_get_name(librados::IoCtx &io_ctx,
+ const std::string &image_name, int r) {
+ bufferlist bl;
+ encode(image_name, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(io_ctx),
+ exec(RBD_DIRECTORY, _, StrEq("rbd"), StrEq("dir_get_name"), _,
+ _, _, _))
+ .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) {
+ *out_bl = bl;
+ })),
+ Return(r)));
+ }
+
+ void expect_get_mirror_info(
+ MockGetMirrorInfoRequest &mock_get_mirror_info_request,
+ const cls::rbd::MirrorImage &mirror_image,
+ librbd::mirror::PromotionState promotion_state,
+ const std::string& primary_mirror_uuid, int r) {
+ EXPECT_CALL(mock_get_mirror_info_request, send())
+ .WillOnce(Invoke([this, &mock_get_mirror_info_request, mirror_image,
+ promotion_state, primary_mirror_uuid, r]() {
+ *mock_get_mirror_info_request.mirror_image = mirror_image;
+ *mock_get_mirror_info_request.promotion_state = promotion_state;
+ *mock_get_mirror_info_request.primary_mirror_uuid =
+ primary_mirror_uuid;
+ m_threads->work_queue->queue(
+ mock_get_mirror_info_request.on_finish, r);
+ }));
+ }
+
+ void expect_trash_move(MockImageDeleter& mock_image_deleter,
+ const std::string& global_image_id,
+ bool ignore_orphan, int r) {
+ EXPECT_CALL(mock_image_deleter,
+ trash_move(global_image_id, ignore_orphan, _))
+ .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) {
+ m_threads->work_queue->queue(ctx, r);
+ })));
+ }
+
+};
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, SuccessJournal) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "local image id",
+ 0);
+ expect_dir_get_name(m_local_io_ctx, "local image name", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_TRUE(mock_state_builder != nullptr);
+ ASSERT_EQ(std::string("local image name"), local_image_name);
+ ASSERT_EQ(std::string("local image id"),
+ mock_journal_state_builder.local_image_id);
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ mock_journal_state_builder.mirror_image_mode);
+ ASSERT_EQ(librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ mock_journal_state_builder.local_promotion_state);
+ ASSERT_EQ(std::string("remote mirror uuid"),
+ mock_journal_state_builder.local_primary_mirror_uuid);
+}
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, SuccessSnapshot) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "local image id",
+ 0);
+ expect_dir_get_name(m_local_io_ctx, "local image name", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockSnapshotStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_TRUE(mock_state_builder != nullptr);
+ ASSERT_EQ(std::string("local image name"), local_image_name);
+ ASSERT_EQ(std::string("local image id"),
+ mock_journal_state_builder.local_image_id);
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ mock_journal_state_builder.mirror_image_mode);
+ ASSERT_EQ(librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ mock_journal_state_builder.local_promotion_state);
+}
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, MirrorImageIdError) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "", -EINVAL);
+
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, DirGetNameDNE) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "local image id",
+ 0);
+ expect_dir_get_name(m_local_io_ctx, "", -ENOENT);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, DirGetNameError) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "local image id",
+ 0);
+ expect_dir_get_name(m_local_io_ctx, "", -EPERM);
+
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, MirrorImageInfoError) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "local image id",
+ 0);
+ expect_dir_get_name(m_local_io_ctx, "local image name", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ "remote mirror uuid", -EINVAL);
+
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, ImageCreating) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "local image id",
+ 0);
+ expect_dir_get_name(m_local_io_ctx, "local image name", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_CREATING},
+ librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockImageDeleter mock_image_deleter;
+ expect_trash_move(mock_image_deleter, "global image id", false, 0);
+
+ MockSnapshotStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+ ASSERT_TRUE(mock_state_builder == nullptr);
+}
+
+TEST_F(TestMockImageReplayerPrepareLocalImageRequest, ImageDisabling) {
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "local image id",
+ 0);
+ expect_dir_get_name(m_local_io_ctx, "local image name", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_DISABLING},
+ librbd::mirror::PROMOTION_STATE_NON_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockSnapshotStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ std::string local_image_name;
+ C_SaferCond ctx;
+ auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx,
+ "global image id",
+ &local_image_name,
+ &mock_state_builder,
+ m_threads->work_queue,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ERESTART, ctx.wait());
+ ASSERT_TRUE(mock_state_builder == nullptr);
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
diff --git a/src/test/rbd_mirror/image_replayer/test_mock_PrepareRemoteImageRequest.cc b/src/test/rbd_mirror/image_replayer/test_mock_PrepareRemoteImageRequest.cc
new file mode 100644
index 000000000..e5b473c0f
--- /dev/null
+++ b/src/test/rbd_mirror/image_replayer/test_mock_PrepareRemoteImageRequest.cc
@@ -0,0 +1,811 @@
+// -*- 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 "cls/rbd/cls_rbd_types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "librbd/mirror/GetInfoRequest.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/GetMirrorImageIdRequest.h"
+#include "tools/rbd_mirror/image_replayer/PrepareRemoteImageRequest.h"
+#include "tools/rbd_mirror/image_replayer/StateBuilder.h"
+#include "tools/rbd_mirror/image_replayer/journal/StateBuilder.h"
+#include "tools/rbd_mirror/image_replayer/snapshot/StateBuilder.h"
+#include "test/journal/mock/MockJournaler.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public librbd::MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace journal {
+
+template <>
+struct TypeTraits<MockTestImageCtx> {
+ typedef ::journal::MockJournalerProxy Journaler;
+};
+
+} // namespace journal
+
+namespace mirror {
+
+template<>
+struct GetInfoRequest<librbd::MockTestImageCtx> {
+ static GetInfoRequest* s_instance;
+ cls::rbd::MirrorImage *mirror_image;
+ PromotionState *promotion_state;
+ std::string *primary_mirror_uuid;
+ Context *on_finish = nullptr;
+
+ static GetInfoRequest* create(librados::IoCtx& io_ctx,
+ librbd::asio::ContextWQ* context_wq,
+ const std::string& image_id,
+ cls::rbd::MirrorImage *mirror_image,
+ PromotionState *promotion_state,
+ std::string* primary_mirror_uuid,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->mirror_image = mirror_image;
+ s_instance->promotion_state = promotion_state;
+ s_instance->primary_mirror_uuid = primary_mirror_uuid;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ GetInfoRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+ ~GetInfoRequest() {
+ s_instance = nullptr;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+GetInfoRequest<librbd::MockTestImageCtx>* GetInfoRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace mirror
+} // namespace librbd
+
+namespace rbd {
+namespace mirror {
+
+template <>
+struct Threads<librbd::MockTestImageCtx> {
+ ceph::mutex &timer_lock;
+ SafeTimer *timer;
+ librbd::asio::ContextWQ *work_queue;
+
+ Threads(Threads<librbd::ImageCtx> *threads)
+ : timer_lock(threads->timer_lock), timer(threads->timer),
+ work_queue(threads->work_queue) {
+ }
+};
+
+namespace image_replayer {
+
+template <>
+struct GetMirrorImageIdRequest<librbd::MockTestImageCtx> {
+ static GetMirrorImageIdRequest* s_instance;
+ std::string* image_id = nullptr;
+ Context* on_finish = nullptr;
+
+ static GetMirrorImageIdRequest* create(librados::IoCtx& io_ctx,
+ const std::string& global_image_id,
+ std::string* image_id,
+ Context* on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->image_id = image_id;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ GetMirrorImageIdRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ std::string local_image_id;
+ librbd::mirror::PromotionState local_promotion_state =
+ librbd::mirror::PROMOTION_STATE_NON_PRIMARY;
+ std::string remote_image_id;
+ std::string remote_mirror_uuid;
+ librbd::mirror::PromotionState remote_promotion_state;
+
+ virtual ~StateBuilder() {}
+
+ MOCK_CONST_METHOD0(get_mirror_image_mode, cls::rbd::MirrorImageMode());
+};
+
+GetMirrorImageIdRequest<librbd::MockTestImageCtx>* GetMirrorImageIdRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+namespace journal {
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx>
+ : public image_replayer::StateBuilder<librbd::MockTestImageCtx> {
+ static StateBuilder* s_instance;
+
+ cls::rbd::MirrorImageMode mirror_image_mode =
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL;
+
+ ::journal::MockJournalerProxy* remote_journaler = nullptr;
+ cls::journal::ClientState remote_client_state;
+ librbd::journal::MirrorPeerClientMeta remote_client_meta;
+
+ static StateBuilder* create(const std::string&) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ StateBuilder() {
+ s_instance = this;
+ }
+};
+
+StateBuilder<librbd::MockTestImageCtx>* StateBuilder<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace journal
+
+namespace snapshot {
+
+template<>
+struct StateBuilder<librbd::MockTestImageCtx>
+ : public image_replayer::StateBuilder<librbd::MockTestImageCtx> {
+ static StateBuilder* s_instance;
+
+ cls::rbd::MirrorImageMode mirror_image_mode =
+ cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT;
+
+ std::string remote_mirror_peer_uuid;
+
+ static StateBuilder* create(const std::string&) {
+ ceph_assert(s_instance != nullptr);
+ return s_instance;
+ }
+
+ StateBuilder() {
+ s_instance = this;
+ }
+};
+
+StateBuilder<librbd::MockTestImageCtx>* StateBuilder<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd
+
+// template definitions
+#include "tools/rbd_mirror/image_replayer/PrepareRemoteImageRequest.cc"
+
+namespace rbd {
+namespace mirror {
+namespace image_replayer {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockImageReplayerPrepareRemoteImageRequest : public TestMockFixture {
+public:
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef PrepareRemoteImageRequest<librbd::MockTestImageCtx> MockPrepareRemoteImageRequest;
+ typedef GetMirrorImageIdRequest<librbd::MockTestImageCtx> MockGetMirrorImageIdRequest;
+ typedef StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+ typedef journal::StateBuilder<librbd::MockTestImageCtx> MockJournalStateBuilder;
+ typedef snapshot::StateBuilder<librbd::MockTestImageCtx> MockSnapshotStateBuilder;
+ typedef librbd::mirror::GetInfoRequest<librbd::MockTestImageCtx> MockGetMirrorInfoRequest;
+
+ void expect_get_mirror_image_mode(MockStateBuilder& mock_state_builder,
+ cls::rbd::MirrorImageMode mirror_image_mode) {
+ EXPECT_CALL(mock_state_builder, get_mirror_image_mode())
+ .WillOnce(Return(mirror_image_mode));
+ }
+
+ void expect_get_mirror_image_id(MockGetMirrorImageIdRequest& mock_get_mirror_image_id_request,
+ const std::string& image_id, int r) {
+ EXPECT_CALL(mock_get_mirror_image_id_request, send())
+ .WillOnce(Invoke([&mock_get_mirror_image_id_request, image_id, r]() {
+ *mock_get_mirror_image_id_request.image_id = image_id;
+ mock_get_mirror_image_id_request.on_finish->complete(r);
+ }));
+ }
+
+ void expect_get_mirror_info(
+ MockGetMirrorInfoRequest &mock_get_mirror_info_request,
+ const cls::rbd::MirrorImage &mirror_image,
+ librbd::mirror::PromotionState promotion_state,
+ const std::string& primary_mirror_uuid, int r) {
+ EXPECT_CALL(mock_get_mirror_info_request, send())
+ .WillOnce(Invoke([this, &mock_get_mirror_info_request, mirror_image,
+ promotion_state, primary_mirror_uuid, r]() {
+ *mock_get_mirror_info_request.mirror_image = mirror_image;
+ *mock_get_mirror_info_request.promotion_state = promotion_state;
+ *mock_get_mirror_info_request.primary_mirror_uuid =
+ primary_mirror_uuid;
+ m_threads->work_queue->queue(
+ mock_get_mirror_info_request.on_finish, r);
+ }));
+ }
+
+ void expect_journaler_get_client(::journal::MockJournaler &mock_journaler,
+ const std::string &client_id,
+ cls::journal::Client &client, int r) {
+ EXPECT_CALL(mock_journaler, get_client(StrEq(client_id), _, _))
+ .WillOnce(DoAll(WithArg<1>(Invoke([client](cls::journal::Client *out_client) {
+ *out_client = client;
+ })),
+ WithArg<2>(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ }))));
+ }
+
+ void expect_journaler_register_client(::journal::MockJournaler &mock_journaler,
+ const librbd::journal::ClientData &client_data,
+ int r) {
+ bufferlist bl;
+ encode(client_data, bl);
+
+ EXPECT_CALL(mock_journaler, register_client(ContentsEqual(bl), _))
+ .WillOnce(WithArg<1>(Invoke([this, r](Context *on_finish) {
+ m_threads->work_queue->queue(on_finish, r);
+ })));
+ }
+};
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, SuccessJournal) {
+ ::journal::MockJournaler mock_remote_journaler;
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", 0);
+
+ EXPECT_CALL(mock_remote_journaler, construct());
+
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.image_id = "local image id";
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING;
+ librbd::journal::ClientData client_data{mirror_peer_client_meta};
+ cls::journal::Client client;
+ client.state = cls::journal::CLIENT_STATE_DISCONNECTED;
+ encode(client_data, client.data);
+ expect_journaler_get_client(mock_remote_journaler, "local mirror uuid",
+ client, 0);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_TRUE(mock_state_builder != nullptr);
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ mock_journal_state_builder.mirror_image_mode);
+ ASSERT_EQ(std::string("remote mirror uuid"),
+ mock_journal_state_builder.remote_mirror_uuid);
+ ASSERT_EQ(std::string("remote image id"),
+ mock_journal_state_builder.remote_image_id);
+ ASSERT_EQ(librbd::mirror::PROMOTION_STATE_PRIMARY,
+ mock_journal_state_builder.remote_promotion_state);
+ ASSERT_TRUE(mock_journal_state_builder.remote_journaler != nullptr);
+ ASSERT_EQ(cls::journal::CLIENT_STATE_DISCONNECTED,
+ mock_journal_state_builder.remote_client_state);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, SuccessSnapshot) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockSnapshotStateBuilder mock_snapshot_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid",
+ "remote mirror peer uuid"},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_TRUE(mock_state_builder != nullptr);
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ mock_snapshot_state_builder.mirror_image_mode);
+ ASSERT_EQ(std::string("remote mirror uuid"),
+ mock_snapshot_state_builder.remote_mirror_uuid);
+ ASSERT_EQ(std::string("remote mirror peer uuid"),
+ mock_snapshot_state_builder.remote_mirror_peer_uuid);
+ ASSERT_EQ(std::string("remote image id"),
+ mock_snapshot_state_builder.remote_image_id);
+ ASSERT_EQ(librbd::mirror::PROMOTION_STATE_PRIMARY,
+ mock_snapshot_state_builder.remote_promotion_state);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, SuccessNotRegistered) {
+ ::journal::MockJournaler mock_remote_journaler;
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ expect_get_mirror_image_mode(mock_journal_state_builder,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL);
+
+ EXPECT_CALL(mock_remote_journaler, construct());
+
+ cls::journal::Client client;
+ expect_journaler_get_client(mock_remote_journaler, "local mirror uuid",
+ client, -ENOENT);
+
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.image_id = "local image id";
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ librbd::journal::ClientData client_data{mirror_peer_client_meta};
+ expect_journaler_register_client(mock_remote_journaler, client_data, 0);
+
+ mock_journal_state_builder.local_image_id = "local image id";
+ MockStateBuilder* mock_state_builder = &mock_journal_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(0, ctx.wait());
+ ASSERT_TRUE(mock_state_builder != nullptr);
+ ASSERT_EQ(std::string("remote image id"),
+ mock_journal_state_builder.remote_image_id);
+ ASSERT_EQ(librbd::mirror::PROMOTION_STATE_PRIMARY,
+ mock_journal_state_builder.remote_promotion_state);
+ ASSERT_TRUE(mock_journal_state_builder.remote_journaler != nullptr);
+ ASSERT_EQ(cls::journal::CLIENT_STATE_CONNECTED,
+ mock_journal_state_builder.remote_client_state);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, GetMirrorImageIdError) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "", -EINVAL);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = &mock_journal_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+ ASSERT_TRUE(mock_journal_state_builder.remote_journaler == nullptr);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, GetMirrorInfoError) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", -EINVAL);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+ ASSERT_TRUE(mock_state_builder == nullptr);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, GetClientError) {
+ ::journal::MockJournaler mock_remote_journaler;
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", 0);
+
+ EXPECT_CALL(mock_remote_journaler, construct());
+
+ cls::journal::Client client;
+ expect_journaler_get_client(mock_remote_journaler, "local mirror uuid",
+ client, -EINVAL);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = nullptr;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+ ASSERT_TRUE(mock_state_builder == nullptr);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, RegisterClientError) {
+ ::journal::MockJournaler mock_remote_journaler;
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ expect_get_mirror_image_mode(mock_journal_state_builder,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL);
+
+ EXPECT_CALL(mock_remote_journaler, construct());
+
+ cls::journal::Client client;
+ expect_journaler_get_client(mock_remote_journaler, "local mirror uuid",
+ client, -ENOENT);
+
+ librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta;
+ mirror_peer_client_meta.image_id = "local image id";
+ mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING;
+ librbd::journal::ClientData client_data{mirror_peer_client_meta};
+ expect_journaler_register_client(mock_remote_journaler, client_data, -EINVAL);
+
+ mock_journal_state_builder.local_image_id = "local image id";
+ MockStateBuilder* mock_state_builder = &mock_journal_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorImageIdDNEJournal) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "", -ENOENT);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = &mock_journal_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ mock_journal_state_builder.mirror_image_mode);
+ ASSERT_EQ("remote mirror uuid",
+ mock_journal_state_builder.remote_mirror_uuid);
+ ASSERT_EQ("", mock_journal_state_builder.remote_image_id);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorImageIdDNESnapshot) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request, "", -ENOENT);
+
+ MockSnapshotStateBuilder mock_snapshot_state_builder;
+ MockStateBuilder* mock_state_builder = &mock_snapshot_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid",
+ "remote mirror peer uuid"},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ mock_snapshot_state_builder.mirror_image_mode);
+ ASSERT_EQ("remote mirror uuid",
+ mock_snapshot_state_builder.remote_mirror_uuid);
+ ASSERT_EQ("remote mirror peer uuid",
+ mock_snapshot_state_builder.remote_mirror_peer_uuid);
+ ASSERT_EQ("", mock_snapshot_state_builder.remote_image_id);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorInfoDNEJournal) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", -ENOENT);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ MockStateBuilder* mock_state_builder = &mock_journal_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ mock_journal_state_builder.mirror_image_mode);
+ ASSERT_EQ("remote mirror uuid",
+ mock_journal_state_builder.remote_mirror_uuid);
+ ASSERT_EQ("", mock_journal_state_builder.remote_image_id);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorInfoDNESnapshot) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", -ENOENT);
+
+ MockSnapshotStateBuilder mock_snapshot_state_builder;
+ MockStateBuilder* mock_state_builder = &mock_snapshot_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid",
+ "remote mirror peer uuid"},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ mock_snapshot_state_builder.mirror_image_mode);
+ ASSERT_EQ("remote mirror uuid",
+ mock_snapshot_state_builder.remote_mirror_uuid);
+ ASSERT_EQ("remote mirror peer uuid",
+ mock_snapshot_state_builder.remote_mirror_peer_uuid);
+ ASSERT_EQ("", mock_snapshot_state_builder.remote_image_id);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorInfoDisablingJournal) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_DISABLING},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockJournalStateBuilder mock_journal_state_builder;
+ expect_get_mirror_image_mode(mock_journal_state_builder,
+ cls::rbd::MIRROR_IMAGE_MODE_JOURNAL);
+ MockStateBuilder* mock_state_builder = &mock_journal_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid", ""},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_JOURNAL,
+ mock_journal_state_builder.mirror_image_mode);
+ ASSERT_EQ("remote mirror uuid",
+ mock_journal_state_builder.remote_mirror_uuid);
+ ASSERT_EQ("", mock_journal_state_builder.remote_image_id);
+}
+
+TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorInfoDisablingSnapshot) {
+ MockThreads mock_threads(m_threads);
+
+ InSequence seq;
+ MockGetMirrorImageIdRequest mock_get_mirror_image_id_request;
+ expect_get_mirror_image_id(mock_get_mirror_image_id_request,
+ "remote image id", 0);
+
+ MockGetMirrorInfoRequest mock_get_mirror_info_request;
+ expect_get_mirror_info(mock_get_mirror_info_request,
+ {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ "global image id",
+ cls::rbd::MIRROR_IMAGE_STATE_DISABLING},
+ librbd::mirror::PROMOTION_STATE_PRIMARY,
+ "remote mirror uuid", 0);
+
+ MockSnapshotStateBuilder mock_snapshot_state_builder;
+ expect_get_mirror_image_mode(mock_snapshot_state_builder,
+ cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT);
+ MockStateBuilder* mock_state_builder = &mock_snapshot_state_builder;
+ C_SaferCond ctx;
+ auto req = MockPrepareRemoteImageRequest::create(&mock_threads,
+ m_local_io_ctx,
+ m_remote_io_ctx,
+ "global image id",
+ "local mirror uuid",
+ {"remote mirror uuid",
+ "remote mirror peer uuid"},
+ nullptr,
+ &mock_state_builder,
+ &ctx);
+ req->send();
+
+ ASSERT_EQ(-ENOENT, ctx.wait());
+ ASSERT_EQ(cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT,
+ mock_snapshot_state_builder.mirror_image_mode);
+ ASSERT_EQ("remote mirror uuid",
+ mock_snapshot_state_builder.remote_mirror_uuid);
+ ASSERT_EQ("remote mirror peer uuid",
+ mock_snapshot_state_builder.remote_mirror_peer_uuid);
+ ASSERT_EQ("", mock_snapshot_state_builder.remote_image_id);
+}
+
+} // namespace image_replayer
+} // namespace mirror
+} // namespace rbd