summaryrefslogtreecommitdiffstats
path: root/src/test/rbd_mirror/test_mock_ImageReplayer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/rbd_mirror/test_mock_ImageReplayer.cc')
-rw-r--r--src/test/rbd_mirror/test_mock_ImageReplayer.cc950
1 files changed, 950 insertions, 0 deletions
diff --git a/src/test/rbd_mirror/test_mock_ImageReplayer.cc b/src/test/rbd_mirror/test_mock_ImageReplayer.cc
new file mode 100644
index 000000000..177b71a15
--- /dev/null
+++ b/src/test/rbd_mirror/test_mock_ImageReplayer.cc
@@ -0,0 +1,950 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "cls/journal/cls_journal_types.h"
+#include "librbd/journal/Types.h"
+#include "librbd/journal/TypeTraits.h"
+#include "tools/rbd_mirror/ImageDeleter.h"
+#include "tools/rbd_mirror/ImageReplayer.h"
+#include "tools/rbd_mirror/InstanceWatcher.h"
+#include "tools/rbd_mirror/MirrorStatusUpdater.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_replayer/BootstrapRequest.h"
+#include "tools/rbd_mirror/image_replayer/Replayer.h"
+#include "tools/rbd_mirror/image_replayer/ReplayerListener.h"
+#include "tools/rbd_mirror/image_replayer/StateBuilder.h"
+#include "tools/rbd_mirror/image_replayer/Utils.h"
+#include "test/rbd_mirror/test_mock_fixture.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/rbd_mirror/mock/MockContextWQ.h"
+#include "test/rbd_mirror/mock/MockSafeTimer.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx &image_ctx)
+ : librbd::MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+} // 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,
+ MockContextWQ* 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;
+
+template <>
+struct MirrorStatusUpdater<librbd::MockTestImageCtx> {
+
+ MOCK_METHOD1(exists, bool(const std::string&));
+ MOCK_METHOD3(set_mirror_image_status,
+ void(const std::string&, const cls::rbd::MirrorImageSiteStatus&,
+ bool));
+ MOCK_METHOD2(remove_refresh_mirror_image_status, void(const std::string&,
+ Context*));
+ MOCK_METHOD3(remove_mirror_image_status, void(const std::string&, bool,
+ Context*));
+};
+
+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;
+ }
+};
+
+template<>
+class InstanceWatcher<librbd::MockTestImageCtx> {
+};
+
+namespace image_replayer {
+
+template<>
+struct BootstrapRequest<librbd::MockTestImageCtx> {
+ static BootstrapRequest* s_instance;
+
+ StateBuilder<librbd::MockTestImageCtx>** state_builder = nullptr;
+ bool *do_resync = nullptr;
+ Context *on_finish = nullptr;
+
+ static BootstrapRequest* create(
+ Threads<librbd::MockTestImageCtx>* threads,
+ librados::IoCtx &local_io_ctx,
+ librados::IoCtx& remote_io_ctx,
+ rbd::mirror::InstanceWatcher<librbd::MockTestImageCtx> *instance_watcher,
+ const std::string &global_image_id,
+ const std::string &local_mirror_uuid,
+ const RemotePoolMeta& remote_pool_meta,
+ ::journal::CacheManagerHandler *cache_manager_handler,
+ PoolMetaCache* pool_meta_cache,
+ rbd::mirror::ProgressContext *progress_ctx,
+ StateBuilder<librbd::MockTestImageCtx>** state_builder,
+ bool *do_resync, Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->state_builder = state_builder;
+ s_instance->do_resync = do_resync;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ BootstrapRequest() {
+ ceph_assert(s_instance == nullptr);
+ s_instance = this;
+ }
+
+ ~BootstrapRequest() {
+ ceph_assert(s_instance == this);
+ s_instance = nullptr;
+ }
+
+ void put() {
+ }
+
+ void get() {
+ }
+
+ std::string get_local_image_name() const {
+ return "local image name";
+ }
+
+ inline bool is_syncing() const {
+ return false;
+ }
+
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD0(cancel, void());
+};
+
+struct MockReplayer : public Replayer {
+ image_replayer::ReplayerListener* replayer_listener;
+
+ MOCK_METHOD0(destroy, void());
+
+ MOCK_METHOD1(init, void(Context*));
+ MOCK_METHOD1(shut_down, void(Context*));
+ MOCK_METHOD1(flush, void(Context*));
+
+ MOCK_METHOD2(get_replay_status, bool(std::string*, Context*));
+
+ MOCK_CONST_METHOD0(is_replaying, bool());
+ MOCK_CONST_METHOD0(is_resync_requested, bool());
+ MOCK_CONST_METHOD0(get_error_code, int());
+ MOCK_CONST_METHOD0(get_error_description, std::string());
+};
+
+template <>
+struct StateBuilder<librbd::MockTestImageCtx> {
+ static StateBuilder* s_instance;
+
+ librbd::MockTestImageCtx* local_image_ctx = nullptr;
+ std::string local_image_id;
+ std::string remote_image_id;
+
+ void destroy() {
+ }
+
+ MOCK_METHOD1(close, void(Context*));
+ MOCK_METHOD5(create_replayer, Replayer*(Threads<librbd::MockTestImageCtx>*,
+ InstanceWatcher<librbd::MockTestImageCtx>*,
+ const std::string&, PoolMetaCache*,
+ ReplayerListener*));
+
+ StateBuilder() {
+ s_instance = this;
+ }
+};
+
+BootstrapRequest<librbd::MockTestImageCtx>* BootstrapRequest<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/ImageReplayer.cc"
+
+namespace rbd {
+namespace mirror {
+
+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::SetArgPointee;
+using ::testing::WithArg;
+
+class TestMockImageReplayer : public TestMockFixture {
+public:
+ typedef Threads<librbd::MockTestImageCtx> MockThreads;
+ typedef ImageDeleter<librbd::MockTestImageCtx> MockImageDeleter;
+ typedef MirrorStatusUpdater<librbd::MockTestImageCtx> MockMirrorStatusUpdater;
+ typedef image_replayer::BootstrapRequest<librbd::MockTestImageCtx> MockBootstrapRequest;
+ typedef image_replayer::StateBuilder<librbd::MockTestImageCtx> MockStateBuilder;
+ typedef image_replayer::MockReplayer MockReplayer;
+ typedef ImageReplayer<librbd::MockTestImageCtx> MockImageReplayer;
+ typedef InstanceWatcher<librbd::MockTestImageCtx> MockInstanceWatcher;
+
+ 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 TearDown() override {
+ delete m_image_replayer;
+
+ TestMockFixture::TearDown();
+ }
+
+ void create_local_image() {
+ 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_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_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);
+ })));
+ }
+
+ bufferlist encode_tag_data(const librbd::journal::TagData &tag_data) {
+ bufferlist bl;
+ encode(tag_data, bl);
+ return bl;
+ }
+
+ void expect_send(MockBootstrapRequest& mock_bootstrap_request,
+ MockStateBuilder& mock_state_builder,
+ librbd::MockTestImageCtx& mock_local_image_ctx,
+ bool do_resync, bool set_local_image, int r) {
+ EXPECT_CALL(mock_bootstrap_request, send())
+ .WillOnce(Invoke([this, &mock_bootstrap_request, &mock_state_builder,
+ &mock_local_image_ctx, set_local_image, do_resync,
+ r]() {
+ if (r == 0 || r == -ENOLINK) {
+ mock_state_builder.local_image_id = mock_local_image_ctx.id;
+ mock_state_builder.remote_image_id = m_remote_image_ctx->id;
+ *mock_bootstrap_request.state_builder = &mock_state_builder;
+ }
+ if (r == 0) {
+ mock_state_builder.local_image_ctx = &mock_local_image_ctx;
+ *mock_bootstrap_request.do_resync = do_resync;
+ }
+ if (r < 0 && r != -ENOENT) {
+ mock_state_builder.remote_image_id = "";
+ }
+ if (r == -ENOENT) {
+ *mock_bootstrap_request.state_builder = &mock_state_builder;
+ }
+ if (set_local_image) {
+ mock_state_builder.local_image_id = mock_local_image_ctx.id;
+ }
+ mock_bootstrap_request.on_finish->complete(r);
+ }));
+ }
+
+ void expect_create_replayer(MockStateBuilder& mock_state_builder,
+ MockReplayer& mock_replayer) {
+ EXPECT_CALL(mock_state_builder, create_replayer(_, _, _, _, _))
+ .WillOnce(WithArg<4>(
+ Invoke([&mock_replayer]
+ (image_replayer::ReplayerListener* replayer_listener) {
+ mock_replayer.replayer_listener = replayer_listener;
+ return &mock_replayer;
+ })));
+ }
+
+ void expect_close(MockStateBuilder& mock_state_builder, int r) {
+ EXPECT_CALL(mock_state_builder, close(_))
+ .WillOnce(Invoke([this, r](Context* ctx) {
+ m_threads->work_queue->queue(ctx, r);
+ }));
+ }
+
+ void expect_init(MockReplayer& mock_replayer, int r) {
+ EXPECT_CALL(mock_replayer, init(_))
+ .WillOnce(Invoke([this, r](Context* ctx) {
+ m_threads->work_queue->queue(ctx, r);
+ }));
+ }
+
+ void expect_shut_down(MockReplayer& mock_replayer, int r) {
+ EXPECT_CALL(mock_replayer, shut_down(_))
+ .WillOnce(Invoke([this, r](Context* ctx) {
+ m_threads->work_queue->queue(ctx, r);
+ }));
+ EXPECT_CALL(mock_replayer, destroy());
+ }
+
+ void expect_get_replay_status(MockReplayer& mock_replayer) {
+ EXPECT_CALL(mock_replayer, get_replay_status(_, _))
+ .WillRepeatedly(DoAll(WithArg<1>(CompleteContext(-EEXIST)),
+ Return(true)));
+ }
+
+ void expect_set_mirror_image_status_repeatedly() {
+ EXPECT_CALL(m_local_status_updater, set_mirror_image_status(_, _, _))
+ .WillRepeatedly(Invoke([](auto, auto, auto){}));
+ EXPECT_CALL(m_remote_status_updater, set_mirror_image_status(_, _, _))
+ .WillRepeatedly(Invoke([](auto, auto, auto){}));
+ }
+
+ void expect_mirror_image_status_exists(bool exists) {
+ EXPECT_CALL(m_local_status_updater, exists(_))
+ .WillOnce(Return(exists));
+ EXPECT_CALL(m_remote_status_updater, exists(_))
+ .WillOnce(Return(exists));
+ }
+
+ void create_image_replayer(MockThreads &mock_threads) {
+ m_image_replayer = new MockImageReplayer(
+ m_local_io_ctx, "local_mirror_uuid", "global image id",
+ &mock_threads, &m_instance_watcher, &m_local_status_updater, nullptr,
+ nullptr);
+ m_image_replayer->add_peer({"peer_uuid", m_remote_io_ctx,
+ {"remote mirror uuid",
+ "remote mirror peer uuid"},
+ &m_remote_status_updater});
+ }
+
+ void wait_for_stopped() {
+ for (int i = 0; i < 10000; i++) {
+ if (m_image_replayer->is_stopped()) {
+ break;
+ }
+ usleep(1000);
+ }
+ ASSERT_TRUE(m_image_replayer->is_stopped());
+ }
+
+ librbd::ImageCtx *m_remote_image_ctx;
+ librbd::ImageCtx *m_local_image_ctx = nullptr;
+ MockInstanceWatcher m_instance_watcher;
+ MockMirrorStatusUpdater m_local_status_updater;
+ MockMirrorStatusUpdater m_remote_status_updater;
+ MockImageReplayer *m_image_replayer = nullptr;
+};
+
+TEST_F(TestMockImageReplayer, StartStop) {
+ // START
+
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockReplayer mock_replayer;
+
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, 0);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+ ASSERT_EQ(image_replayer::HEALTH_STATE_OK,
+ m_image_replayer->get_health_state());
+
+ // STOP
+ expect_shut_down(mock_replayer, 0);
+ expect_close(mock_state_builder, 0);
+ expect_mirror_image_status_exists(false);
+
+ C_SaferCond stop_ctx;
+ m_image_replayer->stop(&stop_ctx);
+ ASSERT_EQ(0, stop_ctx.wait());
+ ASSERT_EQ(image_replayer::HEALTH_STATE_OK,
+ m_image_replayer->get_health_state());
+}
+
+TEST_F(TestMockImageReplayer, LocalImagePrimary) {
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, -ENOMSG);
+
+ expect_mirror_image_status_exists(false);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, MetadataCleanup) {
+ // START
+
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockReplayer mock_replayer;
+
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, true, -ENOLINK);
+
+ expect_close(mock_state_builder, 0);
+ expect_trash_move(mock_image_deleter, "global image id", false, 0);
+ expect_mirror_image_status_exists(false);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, BootstrapRemoteDeleted) {
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+
+ MockBootstrapRequest mock_bootstrap_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, -ENOLINK);
+
+ expect_close(mock_state_builder, 0);
+
+ expect_trash_move(mock_image_deleter, "global image id", false, 0);
+ expect_mirror_image_status_exists(false);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, BootstrapResyncRequested) {
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+
+ MockBootstrapRequest mock_bootstrap_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ true, false, 0);
+
+ expect_close(mock_state_builder, 0);
+
+ expect_trash_move(mock_image_deleter, "global image id", true, 0);
+ expect_mirror_image_status_exists(false);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, BootstrapError) {
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, -EINVAL);
+
+ expect_mirror_image_status_exists(false);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(-EINVAL, start_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, BootstrapCancel) {
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+
+ create_image_replayer(mock_threads);
+
+ MockBootstrapRequest mock_bootstrap_request;
+ MockStateBuilder mock_state_builder;
+ EXPECT_CALL(mock_bootstrap_request, send())
+ .WillOnce(Invoke([this, &mock_bootstrap_request]() {
+ m_image_replayer->stop(nullptr);
+ mock_bootstrap_request.on_finish->complete(-ECANCELED);
+ }));
+ EXPECT_CALL(mock_bootstrap_request, cancel());
+
+ expect_mirror_image_status_exists(false);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(-ECANCELED, start_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, StopError) {
+ // START
+
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockReplayer mock_replayer;
+
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, 0);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+
+ // STOP (errors are ignored)
+
+ expect_shut_down(mock_replayer, -EINVAL);
+ expect_close(mock_state_builder, -EINVAL);
+ expect_mirror_image_status_exists(false);
+
+ C_SaferCond stop_ctx;
+ m_image_replayer->stop(&stop_ctx);
+ ASSERT_EQ(0, stop_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, ReplayerError) {
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockReplayer mock_replayer;
+
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, -EINVAL);
+ EXPECT_CALL(mock_replayer, get_error_description())
+ .WillOnce(Return("FAIL"));
+
+ EXPECT_CALL(mock_replayer, destroy());
+ expect_close(mock_state_builder, -EINVAL);
+
+ expect_mirror_image_status_exists(false);
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(-EINVAL, start_ctx.wait());
+}
+
+TEST_F(TestMockImageReplayer, ReplayerResync) {
+ // START
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockReplayer mock_replayer;
+
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, 0);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+
+ // NOTIFY
+ EXPECT_CALL(mock_replayer, is_resync_requested())
+ .WillOnce(Return(true));
+ expect_shut_down(mock_replayer, 0);
+ expect_close(mock_state_builder, 0);
+ expect_trash_move(mock_image_deleter, "global image id", true, 0);
+ expect_mirror_image_status_exists(false);
+ mock_replayer.replayer_listener->handle_notification();
+ ASSERT_FALSE(m_image_replayer->is_running());
+
+ wait_for_stopped();
+}
+
+TEST_F(TestMockImageReplayer, ReplayerInterrupted) {
+ // START
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockReplayer mock_replayer;
+
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, 0);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+
+ // NOTIFY
+ EXPECT_CALL(mock_replayer, is_resync_requested())
+ .WillOnce(Return(false));
+ EXPECT_CALL(mock_replayer, is_replaying())
+ .WillOnce(Return(false));
+ EXPECT_CALL(mock_replayer, get_error_code())
+ .WillOnce(Return(-EINVAL));
+ EXPECT_CALL(mock_replayer, get_error_description())
+ .WillOnce(Return("INVALID"));
+ expect_shut_down(mock_replayer, 0);
+ expect_close(mock_state_builder, 0);
+ expect_mirror_image_status_exists(false);
+ mock_replayer.replayer_listener->handle_notification();
+ ASSERT_FALSE(m_image_replayer->is_running());
+
+ wait_for_stopped();
+}
+
+TEST_F(TestMockImageReplayer, ReplayerRenamed) {
+ // START
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockImageDeleter mock_image_deleter;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockReplayer mock_replayer;
+
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, 0);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+
+ // NOTIFY
+ EXPECT_CALL(mock_replayer, is_resync_requested())
+ .WillOnce(Return(false));
+ EXPECT_CALL(mock_replayer, is_replaying())
+ .WillOnce(Return(true));
+ mock_local_image_ctx.name = "NEW NAME";
+ mock_replayer.replayer_listener->handle_notification();
+
+ // STOP
+ expect_shut_down(mock_replayer, 0);
+ expect_close(mock_state_builder, 0);
+ expect_mirror_image_status_exists(false);
+
+ C_SaferCond stop_ctx;
+ m_image_replayer->stop(&stop_ctx);
+ ASSERT_EQ(0, stop_ctx.wait());
+
+ auto image_spec = image_replayer::util::compute_image_spec(
+ m_local_io_ctx, "NEW NAME");
+ ASSERT_EQ(image_spec, m_image_replayer->get_name());
+}
+
+TEST_F(TestMockImageReplayer, StopJoinInterruptedReplayer) {
+ // START
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockReplayer mock_replayer;
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, 0);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+
+ // NOTIFY
+ EXPECT_CALL(mock_replayer, is_resync_requested())
+ .WillOnce(Return(false));
+ EXPECT_CALL(mock_replayer, is_replaying())
+ .WillOnce(Return(false));
+ EXPECT_CALL(mock_replayer, get_error_code())
+ .WillOnce(Return(-EINVAL));
+ EXPECT_CALL(mock_replayer, get_error_description())
+ .WillOnce(Return("INVALID"));
+ const double DELAY = 10;
+ EXPECT_CALL(mock_replayer, shut_down(_))
+ .WillOnce(Invoke([this, DELAY](Context* ctx) {
+ std::lock_guard l(m_threads->timer_lock);
+ m_threads->timer->add_event_after(DELAY, ctx);
+ }));
+ EXPECT_CALL(mock_replayer, destroy());
+ expect_close(mock_state_builder, 0);
+ expect_mirror_image_status_exists(false);
+
+ mock_replayer.replayer_listener->handle_notification();
+ ASSERT_FALSE(m_image_replayer->is_running());
+
+ C_SaferCond stop_ctx;
+ m_image_replayer->stop(&stop_ctx);
+ ASSERT_EQ(ETIMEDOUT, stop_ctx.wait_for(DELAY * 3 / 4));
+ ASSERT_EQ(0, stop_ctx.wait_for(DELAY));
+}
+
+TEST_F(TestMockImageReplayer, StopJoinRequestedStop) {
+ // START
+ create_local_image();
+ librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx);
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+ expect_add_event_after_repeatedly(mock_threads);
+
+ MockReplayer mock_replayer;
+ expect_get_replay_status(mock_replayer);
+ expect_set_mirror_image_status_repeatedly();
+
+ InSequence seq;
+ MockBootstrapRequest mock_bootstrap_request;
+ MockStateBuilder mock_state_builder;
+ expect_send(mock_bootstrap_request, mock_state_builder, mock_local_image_ctx,
+ false, false, 0);
+
+ expect_create_replayer(mock_state_builder, mock_replayer);
+ expect_init(mock_replayer, 0);
+
+ create_image_replayer(mock_threads);
+
+ C_SaferCond start_ctx;
+ m_image_replayer->start(&start_ctx);
+ ASSERT_EQ(0, start_ctx.wait());
+
+ // STOP
+ const double DELAY = 10;
+ EXPECT_CALL(mock_replayer, shut_down(_))
+ .WillOnce(Invoke([this, DELAY](Context* ctx) {
+ std::lock_guard l(m_threads->timer_lock);
+ m_threads->timer->add_event_after(DELAY, ctx);
+ }));
+ EXPECT_CALL(mock_replayer, destroy());
+ expect_close(mock_state_builder, 0);
+ expect_mirror_image_status_exists(false);
+
+ C_SaferCond stop_ctx1;
+ m_image_replayer->stop(&stop_ctx1);
+
+ C_SaferCond stop_ctx2;
+ m_image_replayer->stop(&stop_ctx2);
+ ASSERT_EQ(ETIMEDOUT, stop_ctx2.wait_for(DELAY * 3 / 4));
+ ASSERT_EQ(0, stop_ctx2.wait_for(DELAY));
+
+ ASSERT_EQ(0, stop_ctx1.wait_for(0));
+}
+
+} // namespace mirror
+} // namespace rbd