From 483eb2f56657e8e7f419ab1a4fab8dce9ade8609 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 20:24:20 +0200 Subject: Adding upstream version 14.2.21. Signed-off-by: Daniel Baumann --- src/test/rbd_mirror/CMakeLists.txt | 97 ++ .../test_mock_SnapshotPurgeRequest.cc | 397 +++++ .../image_deleter/test_mock_TrashMoveRequest.cc | 738 +++++++++ .../image_deleter/test_mock_TrashRemoveRequest.cc | 452 ++++++ .../image_deleter/test_mock_TrashWatcher.cc | 518 +++++++ src/test/rbd_mirror/image_map/test_Policy.cc | 377 +++++ .../image_replayer/test_mock_BootstrapRequest.cc | 1195 +++++++++++++++ .../image_replayer/test_mock_CreateImageRequest.cc | 758 ++++++++++ .../image_replayer/test_mock_EventPreprocessor.cc | 265 ++++ .../test_mock_GetMirrorImageIdRequest.cc | 106 ++ .../test_mock_PrepareLocalImageRequest.cc | 264 ++++ .../test_mock_PrepareRemoteImageRequest.cc | 396 +++++ .../image_sync/test_mock_SyncPointCreateRequest.cc | 171 +++ .../image_sync/test_mock_SyncPointPruneRequest.cc | 338 +++++ src/test/rbd_mirror/mock/MockContextWQ.h | 18 + src/test/rbd_mirror/mock/MockSafeTimer.h | 16 + .../pool_watcher/test_mock_RefreshImagesRequest.cc | 116 ++ src/test/rbd_mirror/random_write.cc | 213 +++ src/test/rbd_mirror/test_ClusterWatcher.cc | 254 ++++ src/test/rbd_mirror/test_ImageDeleter.cc | 301 ++++ src/test/rbd_mirror/test_ImageReplayer.cc | 1162 ++++++++++++++ src/test/rbd_mirror/test_ImageSync.cc | 347 +++++ src/test/rbd_mirror/test_InstanceWatcher.cc | 132 ++ src/test/rbd_mirror/test_Instances.cc | 165 ++ src/test/rbd_mirror/test_LeaderWatcher.cc | 317 ++++ src/test/rbd_mirror/test_PoolWatcher.cc | 254 ++++ src/test/rbd_mirror/test_fixture.cc | 160 ++ src/test/rbd_mirror/test_fixture.h | 65 + src/test/rbd_mirror/test_main.cc | 53 + src/test/rbd_mirror/test_mock_ImageMap.cc | 1591 ++++++++++++++++++++ src/test/rbd_mirror/test_mock_ImageReplayer.cc | 1397 +++++++++++++++++ src/test/rbd_mirror/test_mock_ImageSync.cc | 430 ++++++ .../rbd_mirror/test_mock_ImageSyncThrottler.cc | 241 +++ src/test/rbd_mirror/test_mock_InstanceReplayer.cc | 366 +++++ src/test/rbd_mirror/test_mock_InstanceWatcher.cc | 988 ++++++++++++ src/test/rbd_mirror/test_mock_LeaderWatcher.cc | 694 +++++++++ src/test/rbd_mirror/test_mock_PoolReplayer.cc | 468 ++++++ src/test/rbd_mirror/test_mock_PoolWatcher.cc | 897 +++++++++++ src/test/rbd_mirror/test_mock_fixture.cc | 64 + src/test/rbd_mirror/test_mock_fixture.h | 71 + 40 files changed, 16852 insertions(+) create mode 100644 src/test/rbd_mirror/CMakeLists.txt create mode 100644 src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc create mode 100644 src/test/rbd_mirror/image_deleter/test_mock_TrashMoveRequest.cc create mode 100644 src/test/rbd_mirror/image_deleter/test_mock_TrashRemoveRequest.cc create mode 100644 src/test/rbd_mirror/image_deleter/test_mock_TrashWatcher.cc create mode 100644 src/test/rbd_mirror/image_map/test_Policy.cc create mode 100644 src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc create mode 100644 src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc create mode 100644 src/test/rbd_mirror/image_replayer/test_mock_EventPreprocessor.cc create mode 100644 src/test/rbd_mirror/image_replayer/test_mock_GetMirrorImageIdRequest.cc create mode 100644 src/test/rbd_mirror/image_replayer/test_mock_PrepareLocalImageRequest.cc create mode 100644 src/test/rbd_mirror/image_replayer/test_mock_PrepareRemoteImageRequest.cc create mode 100644 src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc create mode 100644 src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc create mode 100644 src/test/rbd_mirror/mock/MockContextWQ.h create mode 100644 src/test/rbd_mirror/mock/MockSafeTimer.h create mode 100644 src/test/rbd_mirror/pool_watcher/test_mock_RefreshImagesRequest.cc create mode 100644 src/test/rbd_mirror/random_write.cc create mode 100644 src/test/rbd_mirror/test_ClusterWatcher.cc create mode 100644 src/test/rbd_mirror/test_ImageDeleter.cc create mode 100644 src/test/rbd_mirror/test_ImageReplayer.cc create mode 100644 src/test/rbd_mirror/test_ImageSync.cc create mode 100644 src/test/rbd_mirror/test_InstanceWatcher.cc create mode 100644 src/test/rbd_mirror/test_Instances.cc create mode 100644 src/test/rbd_mirror/test_LeaderWatcher.cc create mode 100644 src/test/rbd_mirror/test_PoolWatcher.cc create mode 100644 src/test/rbd_mirror/test_fixture.cc create mode 100644 src/test/rbd_mirror/test_fixture.h create mode 100644 src/test/rbd_mirror/test_main.cc create mode 100644 src/test/rbd_mirror/test_mock_ImageMap.cc create mode 100644 src/test/rbd_mirror/test_mock_ImageReplayer.cc create mode 100644 src/test/rbd_mirror/test_mock_ImageSync.cc create mode 100644 src/test/rbd_mirror/test_mock_ImageSyncThrottler.cc create mode 100644 src/test/rbd_mirror/test_mock_InstanceReplayer.cc create mode 100644 src/test/rbd_mirror/test_mock_InstanceWatcher.cc create mode 100644 src/test/rbd_mirror/test_mock_LeaderWatcher.cc create mode 100644 src/test/rbd_mirror/test_mock_PoolReplayer.cc create mode 100644 src/test/rbd_mirror/test_mock_PoolWatcher.cc create mode 100644 src/test/rbd_mirror/test_mock_fixture.cc create mode 100644 src/test/rbd_mirror/test_mock_fixture.h (limited to 'src/test/rbd_mirror') diff --git a/src/test/rbd_mirror/CMakeLists.txt b/src/test/rbd_mirror/CMakeLists.txt new file mode 100644 index 00000000..1d8a9757 --- /dev/null +++ b/src/test/rbd_mirror/CMakeLists.txt @@ -0,0 +1,97 @@ +set(rbd_mirror_test_srcs + test_ClusterWatcher.cc + test_PoolWatcher.cc + test_ImageDeleter.cc + test_ImageReplayer.cc + test_ImageSync.cc + test_InstanceWatcher.cc + test_Instances.cc + test_LeaderWatcher.cc + test_fixture.cc + image_map/test_Policy.cc + ) +add_library(rbd_mirror_test STATIC ${rbd_mirror_test_srcs}) +target_link_libraries(rbd_mirror_test + rbd_test_support + GTest::GTest) + +add_executable(unittest_rbd_mirror + test_main.cc + test_mock_fixture.cc + test_mock_ImageMap.cc + test_mock_ImageReplayer.cc + test_mock_ImageSync.cc + test_mock_ImageSyncThrottler.cc + test_mock_InstanceReplayer.cc + test_mock_InstanceWatcher.cc + test_mock_LeaderWatcher.cc + test_mock_PoolReplayer.cc + test_mock_PoolWatcher.cc + image_deleter/test_mock_SnapshotPurgeRequest.cc + image_deleter/test_mock_TrashMoveRequest.cc + image_deleter/test_mock_TrashRemoveRequest.cc + image_deleter/test_mock_TrashWatcher.cc + image_replayer/test_mock_BootstrapRequest.cc + image_replayer/test_mock_CreateImageRequest.cc + image_replayer/test_mock_EventPreprocessor.cc + image_replayer/test_mock_GetMirrorImageIdRequest.cc + image_replayer/test_mock_PrepareLocalImageRequest.cc + image_replayer/test_mock_PrepareRemoteImageRequest.cc + image_sync/test_mock_SyncPointCreateRequest.cc + image_sync/test_mock_SyncPointPruneRequest.cc + pool_watcher/test_mock_RefreshImagesRequest.cc + ) +add_ceph_unittest(unittest_rbd_mirror) + +add_dependencies(unittest_rbd_mirror + cls_journal + cls_lock + cls_rbd) +target_link_libraries(unittest_rbd_mirror + rbd_mirror_test + rados_test_stub + rbd_mirror_internal + rbd_mirror_types + rbd_api + rbd_internal + rbd_test_mock + journal + journal_test_mock + cls_rbd_client + cls_lock_client + cls_journal_client + rbd_types + librados + osdc + global + radostest-cxx + ) + +add_executable(ceph_test_rbd_mirror + test_main.cc + ) + +target_link_libraries(ceph_test_rbd_mirror + rbd_mirror_test + rbd_mirror_internal + rbd_mirror_types + rbd_api + rbd_internal + journal + cls_rbd_client + cls_journal_client + rbd_types + librados + radostest-cxx + ${UNITTEST_LIBS} + ) + +add_executable(ceph_test_rbd_mirror_random_write + random_write.cc) +target_link_libraries(ceph_test_rbd_mirror_random_write + librbd librados global) + +install(TARGETS + ceph_test_rbd_mirror + ceph_test_rbd_mirror_random_write + DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc b/src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc new file mode 100644 index 00000000..35850d3a --- /dev/null +++ b/src/test/rbd_mirror/image_deleter/test_mock_SnapshotPurgeRequest.cc @@ -0,0 +1,397 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/rbd_mirror/test_mock_fixture.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockExclusiveLock.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockImageState.h" +#include "test/librbd/mock/MockOperations.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + static MockTestImageCtx *s_instance; + static MockTestImageCtx *create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx *MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace +} // namespace librbd + +#include "tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.cc" + +namespace rbd { +namespace mirror { +namespace image_deleter { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::WithArg; + +class TestMockImageDeleterSnapshotPurgeRequest : public TestMockFixture { +public: + typedef SnapshotPurgeRequest MockSnapshotPurgeRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::RBD rbd; + ASSERT_EQ(0, create_image(rbd, m_local_io_ctx, m_image_name, m_image_size)); + ASSERT_EQ(0, open_image(m_local_io_ctx, m_image_name, &m_local_image_ctx)); + } + + void expect_set_journal_policy(librbd::MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, set_journal_policy(_)) + .WillOnce(Invoke([](librbd::journal::Policy* policy) { + delete policy; + })); + } + + void expect_open(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(true, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_close(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + })); + } + + void expect_acquire_lock(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, acquire_lock(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + })); + } + + void expect_get_snap_namespace(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, + const cls::rbd::SnapshotNamespace &snap_namespace, + int r) { + EXPECT_CALL(mock_image_ctx, get_snap_namespace(snap_id, _)) + .WillOnce(WithArg<1>(Invoke([snap_namespace, r](cls::rbd::SnapshotNamespace *ns) { + *ns = snap_namespace; + return r; + }))); + } + + void expect_get_snap_name(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, const std::string& name, + int r) { + EXPECT_CALL(mock_image_ctx, get_snap_name(snap_id, _)) + .WillOnce(WithArg<1>(Invoke([name, r](std::string *n) { + *n = name; + return r; + }))); + } + + void expect_is_snap_protected(librbd::MockTestImageCtx &mock_image_ctx, + uint64_t snap_id, bool is_protected, int r) { + EXPECT_CALL(mock_image_ctx, is_snap_protected(snap_id, _)) + .WillOnce(WithArg<1>(Invoke([is_protected, r](bool *prot) { + *prot = is_protected; + return r; + }))); + } + + void expect_snap_unprotect(librbd::MockTestImageCtx &mock_image_ctx, + const cls::rbd::SnapshotNamespace& ns, + const std::string& name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_unprotect(ns, name, _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_snap_remove(librbd::MockTestImageCtx &mock_image_ctx, + const cls::rbd::SnapshotNamespace& ns, + const std::string& name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, execute_snap_remove(ns, name, _)) + .WillOnce(WithArg<2>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_start_op(librbd::MockTestImageCtx &mock_image_ctx, bool success) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, start_op(_)) + .WillOnce(Invoke([success](int* r) { + if (!success) { + *r = -EROFS; + return static_cast(nullptr); + } + return new FunctionContext([](int r) {}); + })); + } + + void expect_destroy(librbd::MockTestImageCtx& mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, destroy()); + } + + librbd::ImageCtx *m_local_image_ctx; +}; + +TEST_F(TestMockImageDeleterSnapshotPurgeRequest, Success) { + { + RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1, + 0, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {}); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap2", 2, + 0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, + {}); + } + + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_acquire_lock(mock_image_ctx, 0); + + expect_get_snap_namespace(mock_image_ctx, 2, + cls::rbd::UserSnapshotNamespace{}, 0); + expect_get_snap_name(mock_image_ctx, 2, "snap2", 0); + expect_is_snap_protected(mock_image_ctx, 2, false, 0); + expect_start_op(mock_image_ctx, true); + expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap2", + 0); + + expect_get_snap_namespace(mock_image_ctx, 1, + cls::rbd::UserSnapshotNamespace{}, 0); + expect_get_snap_name(mock_image_ctx, 1, "snap1", 0); + expect_is_snap_protected(mock_image_ctx, 1, true, 0); + expect_start_op(mock_image_ctx, true); + expect_snap_unprotect(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, + "snap1", 0); + expect_start_op(mock_image_ctx, true); + expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap1", + 0); + + expect_close(mock_image_ctx, 0); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterSnapshotPurgeRequest, OpenError) { + { + RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1, + 0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, + {}); + } + + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, -EPERM); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDeleterSnapshotPurgeRequest, AcquireLockError) { + { + RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1, + 0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, + {}); + } + + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_acquire_lock(mock_image_ctx, -EPERM); + expect_close(mock_image_ctx, -EINVAL); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDeleterSnapshotPurgeRequest, SnapUnprotectBusy) { + { + RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1, + 0, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {}); + } + + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_acquire_lock(mock_image_ctx, 0); + + expect_get_snap_namespace(mock_image_ctx, 1, + cls::rbd::UserSnapshotNamespace{}, 0); + expect_get_snap_name(mock_image_ctx, 1, "snap1", 0); + expect_is_snap_protected(mock_image_ctx, 1, true, 0); + expect_start_op(mock_image_ctx, true); + expect_snap_unprotect(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, + "snap1", -EBUSY); + + expect_close(mock_image_ctx, -EINVAL); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id, + &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); +} + +TEST_F(TestMockImageDeleterSnapshotPurgeRequest, SnapUnprotectError) { + { + RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1, + 0, {}, RBD_PROTECTION_STATUS_PROTECTED, 0, {}); + } + + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_acquire_lock(mock_image_ctx, 0); + + expect_get_snap_namespace(mock_image_ctx, 1, + cls::rbd::UserSnapshotNamespace{}, 0); + expect_get_snap_name(mock_image_ctx, 1, "snap1", 0); + expect_is_snap_protected(mock_image_ctx, 1, true, 0); + expect_start_op(mock_image_ctx, true); + expect_snap_unprotect(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, + "snap1", -EPERM); + + expect_close(mock_image_ctx, -EINVAL); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDeleterSnapshotPurgeRequest, SnapRemoveError) { + { + RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1, + 0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, + {}); + } + + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_acquire_lock(mock_image_ctx, 0); + + expect_get_snap_namespace(mock_image_ctx, 1, + cls::rbd::UserSnapshotNamespace{}, 0); + expect_get_snap_name(mock_image_ctx, 1, "snap1", 0); + expect_is_snap_protected(mock_image_ctx, 1, false, 0); + expect_start_op(mock_image_ctx, true); + expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap1", + -EINVAL); + + expect_close(mock_image_ctx, -EPERM); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterSnapshotPurgeRequest, CloseError) { + { + RWLock::WLocker snap_locker(m_local_image_ctx->snap_lock); + m_local_image_ctx->add_snap(cls::rbd::UserSnapshotNamespace{}, "snap1", 1, + 0, {}, RBD_PROTECTION_STATUS_UNPROTECTED, 0, + {}); + } + + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_acquire_lock(mock_image_ctx, 0); + + expect_get_snap_namespace(mock_image_ctx, 1, + cls::rbd::UserSnapshotNamespace{}, 0); + expect_get_snap_name(mock_image_ctx, 1, "snap1", 0); + expect_is_snap_protected(mock_image_ctx, 1, false, 0); + expect_start_op(mock_image_ctx, true); + expect_snap_remove(mock_image_ctx, cls::rbd::UserSnapshotNamespace{}, "snap1", + 0); + + expect_close(mock_image_ctx, -EINVAL); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockSnapshotPurgeRequest::create(m_local_io_ctx, mock_image_ctx.id, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image_deleter +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/image_deleter/test_mock_TrashMoveRequest.cc b/src/test/rbd_mirror/image_deleter/test_mock_TrashMoveRequest.cc new file mode 100644 index 00000000..b2ec9692 --- /dev/null +++ b/src/test/rbd_mirror/image_deleter/test_mock_TrashMoveRequest.cc @@ -0,0 +1,738 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/rbd_mirror/test_mock_fixture.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/TrashWatcher.h" +#include "librbd/journal/ResetRequest.h" +#include "librbd/trash/MoveRequest.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_deleter/TrashMoveRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockExclusiveLock.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockImageState.h" +#include "test/librbd/mock/MockOperations.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + static MockTestImageCtx *s_instance; + static MockTestImageCtx *create(const std::string &image_name, + const std::string &image_id, + const char *snap, librados::IoCtx& p, + bool read_only) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + s_instance = this; + } +}; + +MockTestImageCtx *MockTestImageCtx::s_instance = nullptr; + +} // anonymous namespace + +template <> +struct Journal { + static Journal *s_instance; + + static void get_tag_owner(librados::IoCtx &io_ctx, + const std::string &image_id, + std::string *mirror_uuid, + ContextWQ *work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->get_tag_owner(image_id, mirror_uuid, on_finish); + } + + MOCK_METHOD3(get_tag_owner, void(const std::string &, std::string *, + Context *)); + + Journal() { + s_instance = this; + } +}; + +Journal* Journal::s_instance = nullptr; + +template<> +struct TrashWatcher { + static TrashWatcher* s_instance; + static void notify_image_added(librados::IoCtx&, const std::string& image_id, + const cls::rbd::TrashImageSpec& spec, + Context *ctx) { + ceph_assert(s_instance != nullptr); + s_instance->notify_image_added(image_id, spec, ctx); + } + + MOCK_METHOD3(notify_image_added, void(const std::string&, + const cls::rbd::TrashImageSpec&, + Context*)); + + TrashWatcher() { + s_instance = this; + } +}; + +TrashWatcher* TrashWatcher::s_instance = nullptr; + +namespace journal { + +template <> +struct ResetRequest { + static ResetRequest* s_instance; + Context* on_finish = nullptr; + + static ResetRequest* create(librados::IoCtx &io_ctx, + const std::string &image_id, + const std::string &client_id, + const std::string &mirror_uuid, + ContextWQ *op_work_queue, Context *on_finish) { + ceph_assert(s_instance != nullptr); + EXPECT_EQ(librbd::Journal<>::LOCAL_MIRROR_UUID, mirror_uuid); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + ResetRequest() { + s_instance = this; + } +}; + +ResetRequest* ResetRequest::s_instance = nullptr; + +} // namespace journal + +namespace trash { + +template <> +struct MoveRequest { + static MoveRequest* s_instance; + Context* on_finish = nullptr; + + typedef boost::optional DefermentEndTime; + + static MoveRequest* create(librados::IoCtx& io_ctx, + const std::string& image_id, + const cls::rbd::TrashImageSpec& trash_image_spec, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->construct(image_id, trash_image_spec); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD2(construct, void(const std::string&, + const cls::rbd::TrashImageSpec&)); + MOCK_METHOD0(send, void()); + + MoveRequest() { + s_instance = this; + } +}; + +MoveRequest* MoveRequest::s_instance = nullptr; + +} // namespace trash +} // namespace librbd + +#include "tools/rbd_mirror/image_deleter/TrashMoveRequest.cc" + +namespace rbd { +namespace mirror { +namespace image_deleter { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; +using ::testing::WithArgs; + +class TestMockImageDeleterTrashMoveRequest : public TestMockFixture { +public: + typedef TrashMoveRequest MockTrashMoveRequest; + typedef librbd::Journal MockJournal; + typedef librbd::journal::ResetRequest MockJournalResetRequest; + typedef librbd::trash::MoveRequest MockLibrbdTrashMoveRequest; + typedef librbd::TrashWatcher MockTrashWatcher; + + 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_mirror_image_get_image_id(const std::string& image_id, int r) { + bufferlist bl; + encode(image_id, bl); + + EXPECT_CALL(get_mock_io_ctx(m_local_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_get_tag_owner(MockJournal &mock_journal, + const std::string &image_id, + const std::string &tag_owner, int r) { + EXPECT_CALL(mock_journal, get_tag_owner(image_id, _, _)) + .WillOnce(WithArgs<1, 2>(Invoke([this, tag_owner, r](std::string *owner, Context *on_finish) { + *owner = tag_owner; + m_threads->work_queue->queue(on_finish, r); + }))); + } + + void expect_set_journal_policy(librbd::MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, set_journal_policy(_)) + .WillOnce(Invoke([](librbd::journal::Policy* policy) { + delete policy; + })); + } + + void expect_open(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, open(true, _)) + .WillOnce(WithArg<1>(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + }))); + } + + void expect_close(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.state, close(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + })); + } + + void expect_block_requests(librbd::MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(0)).Times(1); + } + + void expect_acquire_lock(librbd::MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, acquire_lock(_)) + .WillOnce(Invoke([this, r](Context* ctx) { + m_threads->work_queue->queue(ctx, r); + })); + } + + void expect_destroy(librbd::MockTestImageCtx& mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, destroy()); + } + + void expect_mirror_image_set(const std::string& image_id, + const std::string& global_image_id, + cls::rbd::MirrorImageState mirror_image_state, + int r) { + cls::rbd::MirrorImage mirror_image; + mirror_image.global_image_id = global_image_id; + mirror_image.state = mirror_image_state; + + 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(librados::IoCtx &ioctx, int r) { + EXPECT_CALL(get_mock_io_ctx(ioctx), + exec(StrEq("rbd_mirroring"), _, StrEq("rbd"), StrEq("mirror_image_remove"), + _, _, _)) + .WillOnce(Return(r)); + } + + void expect_journal_reset(MockJournalResetRequest& mock_journal_reset_request, + int r) { + EXPECT_CALL(mock_journal_reset_request, send()) + .WillOnce(Invoke([this, &mock_journal_reset_request, r]() { + m_threads->work_queue->queue(mock_journal_reset_request.on_finish, r); + })); + } + + void expect_trash_move(MockLibrbdTrashMoveRequest& mock_trash_move_request, + const std::string& image_name, + const std::string& image_id, + const boost::optional& delay, int r) { + EXPECT_CALL(mock_trash_move_request, construct(image_id, _)) + .WillOnce(WithArg<1>(Invoke([image_name, delay](const cls::rbd::TrashImageSpec& spec) { + ASSERT_EQ(cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, + spec.source); + ASSERT_EQ(image_name, spec.name); + if (delay) { + utime_t time{spec.deletion_time}; + time += *delay; + ASSERT_TRUE(time == spec.deferment_end_time); + } else { + ASSERT_EQ(spec.deletion_time, spec.deferment_end_time); + } + }))); + EXPECT_CALL(mock_trash_move_request, send()) + .WillOnce(Invoke([this, &mock_trash_move_request, r]() { + m_threads->work_queue->queue(mock_trash_move_request.on_finish, r); + })); + } + + void expect_notify_image_added(MockTrashWatcher& mock_trash_watcher, + const std::string& image_id) { + EXPECT_CALL(mock_trash_watcher, notify_image_added(image_id, _, _)) + .WillOnce(WithArg<2>(Invoke([this](Context *ctx) { + m_threads->work_queue->queue(ctx, 0); + }))); + } + + librbd::ImageCtx *m_local_image_ctx; +}; + +TEST_F(TestMockImageDeleterTrashMoveRequest, Success) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_block_requests(mock_image_ctx); + expect_acquire_lock(mock_image_ctx, 0); + + MockLibrbdTrashMoveRequest mock_librbd_trash_move_request; + expect_trash_move(mock_librbd_trash_move_request, m_image_name, "image id", + {}, 0); + expect_mirror_image_remove(m_local_io_ctx, 0); + + expect_close(mock_image_ctx, 0); + expect_destroy(mock_image_ctx); + + MockTrashWatcher mock_trash_watcher; + expect_notify_image_added(mock_trash_watcher, "image id"); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, GetImageIdDNE) { + InSequence seq; + expect_mirror_image_get_image_id("image id", -ENOENT); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, GetImageIdError) { + InSequence seq; + expect_mirror_image_get_image_id("image id", -EINVAL); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, GetTagOwnerLocalPrimary) { + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::LOCAL_MIRROR_UUID, 0); + + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, GetTagOwnerOrphan) { + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + false, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, GetTagOwnerDNE) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", "remote uuid", -ENOENT); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_block_requests(mock_image_ctx); + expect_acquire_lock(mock_image_ctx, 0); + + MockLibrbdTrashMoveRequest mock_librbd_trash_move_request; + expect_trash_move(mock_librbd_trash_move_request, m_image_name, "image id", + {}, 0); + expect_mirror_image_remove(m_local_io_ctx, 0); + + expect_close(mock_image_ctx, 0); + expect_destroy(mock_image_ctx); + + MockTrashWatcher mock_trash_watcher; + expect_notify_image_added(mock_trash_watcher, "image id"); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, GetTagOwnerError) { + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", "remote uuid", -EINVAL); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, DisableMirrorImageError) { + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, -EINVAL); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, ResetJournalError) { + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, -EINVAL); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, OpenImageError) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, -EINVAL); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, AcquireLockError) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_block_requests(mock_image_ctx); + expect_acquire_lock(mock_image_ctx, -EINVAL); + + expect_close(mock_image_ctx, 0); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, TrashMoveError) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_block_requests(mock_image_ctx); + expect_acquire_lock(mock_image_ctx, 0); + + MockLibrbdTrashMoveRequest mock_librbd_trash_move_request; + expect_trash_move(mock_librbd_trash_move_request, m_image_name, "image id", + {}, -EINVAL); + + expect_close(mock_image_ctx, 0); + expect_destroy(mock_image_ctx); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, RemoveMirrorImageError) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_block_requests(mock_image_ctx); + expect_acquire_lock(mock_image_ctx, 0); + + MockLibrbdTrashMoveRequest mock_librbd_trash_move_request; + expect_trash_move(mock_librbd_trash_move_request, m_image_name, "image id", + {}, 0); + expect_mirror_image_remove(m_local_io_ctx, -EINVAL); + + expect_close(mock_image_ctx, 0); + expect_destroy(mock_image_ctx); + + MockTrashWatcher mock_trash_watcher; + expect_notify_image_added(mock_trash_watcher, "image id"); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, CloseImageError) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_block_requests(mock_image_ctx); + expect_acquire_lock(mock_image_ctx, 0); + + MockLibrbdTrashMoveRequest mock_librbd_trash_move_request; + expect_trash_move(mock_librbd_trash_move_request, m_image_name, "image id", + {}, 0); + expect_mirror_image_remove(m_local_io_ctx, 0); + + expect_close(mock_image_ctx, -EINVAL); + expect_destroy(mock_image_ctx); + + MockTrashWatcher mock_trash_watcher; + expect_notify_image_added(mock_trash_watcher, "image id"); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashMoveRequest, DelayedDelation) { + librbd::MockTestImageCtx mock_image_ctx(*m_local_image_ctx); + librbd::MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.config.set_val("rbd_mirroring_delete_delay", "600"); + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + + InSequence seq; + expect_mirror_image_get_image_id("image id", 0); + + MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "image id", + librbd::Journal<>::ORPHAN_MIRROR_UUID, 0); + expect_mirror_image_set("image id", "global image id", + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, 0); + + MockJournalResetRequest mock_journal_reset_request; + expect_journal_reset(mock_journal_reset_request, 0); + + expect_set_journal_policy(mock_image_ctx); + expect_open(mock_image_ctx, 0); + expect_block_requests(mock_image_ctx); + expect_acquire_lock(mock_image_ctx, 0); + + MockLibrbdTrashMoveRequest mock_librbd_trash_move_request; + expect_trash_move(mock_librbd_trash_move_request, m_image_name, "image id", + 600, 0); + + expect_mirror_image_remove(m_local_io_ctx, 0); + expect_close(mock_image_ctx, 0); + expect_destroy(mock_image_ctx); + + MockTrashWatcher mock_trash_watcher; + expect_notify_image_added(mock_trash_watcher, "image id"); + + C_SaferCond ctx; + auto req = MockTrashMoveRequest::create(m_local_io_ctx, "global image id", + true, + m_local_image_ctx->op_work_queue, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace image_deleter +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/image_deleter/test_mock_TrashRemoveRequest.cc b/src/test/rbd_mirror/image_deleter/test_mock_TrashRemoveRequest.cc new file mode 100644 index 00000000..be875704 --- /dev/null +++ b/src/test/rbd_mirror/image_deleter/test_mock_TrashRemoveRequest.cc @@ -0,0 +1,452 @@ +// -*- 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/ImageCtx.h" +#include "librbd/TrashWatcher.h" +#include "librbd/Utils.h" +#include "librbd/trash/RemoveRequest.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_deleter/SnapshotPurgeRequest.h" +#include "tools/rbd_mirror/image_deleter/TrashRemoveRequest.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 + +template<> +struct TrashWatcher { + static TrashWatcher* s_instance; + static void notify_image_removed(librados::IoCtx&, + const std::string& image_id, Context *ctx) { + ceph_assert(s_instance != nullptr); + s_instance->notify_image_removed(image_id, ctx); + } + + MOCK_METHOD2(notify_image_removed, void(const std::string&, Context*)); + + TrashWatcher() { + s_instance = this; + } +}; + +TrashWatcher* TrashWatcher::s_instance = nullptr; + +namespace trash { + +template <> +struct RemoveRequest { + static RemoveRequest *s_instance; + Context *on_finish = nullptr; + + static RemoveRequest *create(librados::IoCtx &io_ctx, + const std::string &image_id, + ContextWQ *work_queue, + bool force, + librbd::ProgressContext &progress_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + EXPECT_TRUE(force); + s_instance->construct(image_id); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD1(construct, void(const std::string&)); + MOCK_METHOD0(send, void()); + + RemoveRequest() { + s_instance = this; + } +}; + +RemoveRequest* RemoveRequest::s_instance = nullptr; + +} // namespace trash +} // namespace librbd + +namespace rbd { +namespace mirror { +namespace image_deleter { + +template <> +struct SnapshotPurgeRequest { + static SnapshotPurgeRequest *s_instance; + Context *on_finish = nullptr; + + static SnapshotPurgeRequest *create(librados::IoCtx &io_ctx, + const std::string &image_id, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->construct(image_id); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD1(construct, void(const std::string&)); + MOCK_METHOD0(send, void()); + + SnapshotPurgeRequest() { + s_instance = this; + } +}; + +SnapshotPurgeRequest* SnapshotPurgeRequest::s_instance = nullptr; + +} // namespace image_deleter +} // namespace mirror +} // namespace rbd + +#include "tools/rbd_mirror/image_deleter/TrashRemoveRequest.cc" + +namespace rbd { +namespace mirror { +namespace image_deleter { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; +using ::testing::WithArgs; + +class TestMockImageDeleterTrashRemoveRequest : public TestMockFixture { +public: + typedef TrashRemoveRequest MockTrashRemoveRequest; + typedef SnapshotPurgeRequest MockSnapshotPurgeRequest; + typedef librbd::TrashWatcher MockTrashWatcher; + typedef librbd::trash::RemoveRequest MockLibrbdTrashRemoveRequest; + + void expect_trash_get(const cls::rbd::TrashImageSpec& trash_spec, int r) { + using ceph::encode; + EXPECT_CALL(get_mock_io_ctx(m_local_io_ctx), + exec(StrEq(RBD_TRASH), _, StrEq("rbd"), + StrEq("trash_get"), _, _, _)) + .WillOnce(WithArg<5>(Invoke([trash_spec, r](bufferlist* bl) { + encode(trash_spec, *bl); + return r; + }))); + } + + void expect_trash_state_set(const std::string& image_id, int r) { + bufferlist in_bl; + encode(image_id, in_bl); + encode(cls::rbd::TRASH_IMAGE_STATE_REMOVING, in_bl); + encode(cls::rbd::TRASH_IMAGE_STATE_NORMAL, in_bl); + + EXPECT_CALL(get_mock_io_ctx(m_local_io_ctx), + exec(StrEq(RBD_TRASH), _, StrEq("rbd"), + StrEq("trash_state_set"), + ContentsEqual(in_bl), _, _)) + .WillOnce(Return(r)); + } + + void expect_get_snapcontext(const std::string& image_id, + const ::SnapContext &snapc, int r) { + bufferlist bl; + encode(snapc, bl); + + EXPECT_CALL(get_mock_io_ctx(m_local_io_ctx), + exec(librbd::util::header_name(image_id), _, StrEq("rbd"), + StrEq("get_snapcontext"), _, _, _)) + .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) { + *out_bl = bl; + })), + Return(r))); + } + + void expect_snapshot_purge(MockSnapshotPurgeRequest &snapshot_purge_request, + const std::string &image_id, int r) { + EXPECT_CALL(snapshot_purge_request, construct(image_id)); + EXPECT_CALL(snapshot_purge_request, send()) + .WillOnce(Invoke([this, &snapshot_purge_request, r]() { + m_threads->work_queue->queue( + snapshot_purge_request.on_finish, r); + })); + } + + void expect_image_remove(MockLibrbdTrashRemoveRequest &image_remove_request, + const std::string &image_id, int r) { + EXPECT_CALL(image_remove_request, construct(image_id)); + EXPECT_CALL(image_remove_request, send()) + .WillOnce(Invoke([this, &image_remove_request, r]() { + m_threads->work_queue->queue( + image_remove_request.on_finish, r); + })); + } + + void expect_notify_image_removed(MockTrashWatcher& mock_trash_watcher, + const std::string& image_id) { + EXPECT_CALL(mock_trash_watcher, notify_image_removed(image_id, _)) + .WillOnce(WithArg<1>(Invoke([this](Context *ctx) { + m_threads->work_queue->queue(ctx, 0); + }))); + } + +}; + +TEST_F(TestMockImageDeleterTrashRemoveRequest, Success) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", 0); + + expect_get_snapcontext("image id", {1, {1}}, 0); + + MockSnapshotPurgeRequest mock_snapshot_purge_request; + expect_snapshot_purge(mock_snapshot_purge_request, "image id", 0); + + MockLibrbdTrashRemoveRequest mock_image_remove_request; + expect_image_remove(mock_image_remove_request, "image id", 0); + + MockTrashWatcher mock_trash_watcher; + expect_notify_image_removed(mock_trash_watcher, "image id"); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, TrashDNE) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, -ENOENT); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, TrashError) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, -EPERM); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, TrashSourceIncorrect) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_USER, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, TrashStateIncorrect) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + trash_image_spec.state = cls::rbd::TRASH_IMAGE_STATE_RESTORING; + expect_trash_get(trash_image_spec, 0); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); + ASSERT_EQ(ERROR_RESULT_RETRY_IMMEDIATELY, error_result); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, TrashSetStateDNE) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", -ENOENT); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, TrashSetStateError) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", -EPERM); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, GetSnapContextDNE) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", 0); + + expect_get_snapcontext("image id", {1, {1}}, -ENOENT); + + MockLibrbdTrashRemoveRequest mock_image_remove_request; + expect_image_remove(mock_image_remove_request, "image id", 0); + + MockTrashWatcher mock_trash_watcher; + expect_notify_image_removed(mock_trash_watcher, "image id"); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, GetSnapContextError) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", 0); + + expect_get_snapcontext("image id", {1, {1}}, -EINVAL); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, PurgeSnapshotBusy) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", 0); + + expect_get_snapcontext("image id", {1, {1}}, 0); + + MockSnapshotPurgeRequest mock_snapshot_purge_request; + expect_snapshot_purge(mock_snapshot_purge_request, "image id", -EBUSY); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(-EBUSY, ctx.wait()); + ASSERT_EQ(ERROR_RESULT_RETRY_IMMEDIATELY, error_result); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, PurgeSnapshotError) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", 0); + + expect_get_snapcontext("image id", {1, {1}}, 0); + + MockSnapshotPurgeRequest mock_snapshot_purge_request; + expect_snapshot_purge(mock_snapshot_purge_request, "image id", -EINVAL); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageDeleterTrashRemoveRequest, RemoveError) { + InSequence seq; + + cls::rbd::TrashImageSpec trash_image_spec{ + cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "image name", {}, {}}; + expect_trash_get(trash_image_spec, 0); + + expect_trash_state_set("image id", 0); + + expect_get_snapcontext("image id", {1, {1}}, 0); + + MockSnapshotPurgeRequest mock_snapshot_purge_request; + expect_snapshot_purge(mock_snapshot_purge_request, "image id", 0); + + MockLibrbdTrashRemoveRequest mock_image_remove_request; + expect_image_remove(mock_image_remove_request, "image id", -EINVAL); + + C_SaferCond ctx; + ErrorResult error_result; + auto req = MockTrashRemoveRequest::create(m_local_io_ctx, "image id", + &error_result, + m_threads->work_queue, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image_deleter +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/image_deleter/test_mock_TrashWatcher.cc b/src/test/rbd_mirror/image_deleter/test_mock_TrashWatcher.cc new file mode 100644 index 00000000..034c3e54 --- /dev/null +++ b/src/test/rbd_mirror/image_deleter/test_mock_TrashWatcher.cc @@ -0,0 +1,518 @@ +// -*- 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 "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/rbd_mirror/mock/MockContextWQ.h" +#include "test/rbd_mirror/mock/MockSafeTimer.h" +#include "librbd/TrashWatcher.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_deleter/TrashWatcher.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +struct MockTrashWatcher { + static MockTrashWatcher *s_instance; + static MockTrashWatcher &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + MockTrashWatcher() { + s_instance = this; + } + + MOCK_CONST_METHOD0(is_unregistered, bool()); + MOCK_METHOD1(register_watch, void(Context*)); + MOCK_METHOD1(unregister_watch, void(Context*)); +}; + +template <> +struct TrashWatcher { + static TrashWatcher *s_instance; + + TrashWatcher(librados::IoCtx &io_ctx, ::MockContextWQ *work_queue) { + s_instance = this; + } + virtual ~TrashWatcher() { + } + + static TrashWatcher &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + virtual void handle_rewatch_complete(int r) = 0; + + virtual void handle_image_added(const std::string &image_id, + const cls::rbd::TrashImageSpec& spec) = 0; + virtual void handle_image_removed(const std::string &image_id) = 0; + + bool is_unregistered() const { + return MockTrashWatcher::get_instance().is_unregistered(); + } + void register_watch(Context *ctx) { + MockTrashWatcher::get_instance().register_watch(ctx); + } + void unregister_watch(Context *ctx) { + MockTrashWatcher::get_instance().unregister_watch(ctx); + } +}; + +MockTrashWatcher *MockTrashWatcher::s_instance = nullptr; +TrashWatcher *TrashWatcher::s_instance = nullptr; + +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct Threads { + MockSafeTimer *timer; + Mutex &timer_lock; + + MockContextWQ *work_queue; + + Threads(Threads *threads) + : timer(new MockSafeTimer()), + timer_lock(threads->timer_lock), + work_queue(new MockContextWQ()) { + } + ~Threads() { + delete timer; + delete work_queue; + } +}; + +} // namespace mirror +} // namespace rbd + +#include "tools/rbd_mirror/image_deleter/TrashWatcher.cc" + +namespace rbd { +namespace mirror { +namespace image_deleter { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnArg; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageDeleterTrashWatcher : public TestMockFixture { +public: + typedef TrashWatcher MockTrashWatcher; + typedef Threads MockThreads; + typedef librbd::MockTrashWatcher MockLibrbdTrashWatcher; + typedef librbd::TrashWatcher LibrbdTrashWatcher; + + struct MockListener : TrashListener { + MOCK_METHOD2(handle_trash_image, void(const std::string&, const utime_t&)); + }; + + void expect_work_queue(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_trash_watcher_is_unregistered(MockLibrbdTrashWatcher &mock_trash_watcher, + bool unregistered) { + EXPECT_CALL(mock_trash_watcher, is_unregistered()) + .WillOnce(Return(unregistered)); + } + + void expect_trash_watcher_register(MockLibrbdTrashWatcher &mock_trash_watcher, + int r) { + EXPECT_CALL(mock_trash_watcher, register_watch(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_trash_watcher_unregister(MockLibrbdTrashWatcher &mock_trash_watcher, + int r) { + EXPECT_CALL(mock_trash_watcher, unregister_watch(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_create_trash(librados::IoCtx &io_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(io_ctx), create(RBD_TRASH, false)) + .WillOnce(Return(r)); + } + + void expect_trash_list(librados::IoCtx &io_ctx, + const std::string& last_image_id, + std::map&& images, + int r) { + bufferlist bl; + encode(last_image_id, bl); + encode(static_cast(1024), bl); + + bufferlist out_bl; + encode(images, out_bl); + + EXPECT_CALL(get_mock_io_ctx(io_ctx), + exec(RBD_TRASH, _, StrEq("rbd"), StrEq("trash_list"), + ContentsEqual(bl), _, _)) + .WillOnce(DoAll(WithArg<5>(Invoke([out_bl](bufferlist *bl) { + *bl = out_bl; + })), + Return(r))); + } + + void expect_timer_add_event(MockThreads &mock_threads) { + EXPECT_CALL(*mock_threads.timer, add_event_after(_, _)) + .WillOnce(DoAll(WithArg<1>(Invoke([this](Context *ctx) { + auto wrapped_ctx = + new FunctionContext([this, ctx](int r) { + Mutex::Locker timer_locker(m_threads->timer_lock); + ctx->complete(r); + }); + m_threads->work_queue->queue(wrapped_ctx, 0); + })), + ReturnArg<1>())); + } + + void expect_handle_trash_image(MockListener& mock_listener, + const std::string& global_image_id) { + EXPECT_CALL(mock_listener, handle_trash_image(global_image_id, _)); + } + + int when_shut_down(MockTrashWatcher &mock_trash_watcher) { + C_SaferCond ctx; + mock_trash_watcher.shut_down(&ctx); + return ctx.wait(); + } + +}; + +TEST_F(TestMockImageDeleterTrashWatcher, EmptyPool) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + expect_trash_list(m_local_io_ctx, "", {}, 0); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, NonEmptyPool) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + MockListener mock_listener; + expect_handle_trash_image(mock_listener, "image0"); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + + std::map images; + images["image0"] = {cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "name", {}, {}}; + for (auto idx = 1; idx < 1024; ++idx) { + images["image" + stringify(idx)] = {}; + } + expect_trash_list(m_local_io_ctx, "", std::move(images), 0); + + images.clear(); + for (auto idx = 1024; idx < 2000; ++idx) { + images["image" + stringify(idx)] = {}; + } + expect_trash_list(m_local_io_ctx, "image999", std::move(images), 0); + + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + m_threads->work_queue->drain(); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, Notify) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + MockListener mock_listener; + expect_handle_trash_image(mock_listener, "image1"); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + expect_trash_list(m_local_io_ctx, "", {}, 0); + + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + LibrbdTrashWatcher::get_instance().handle_image_added( + "image1", {cls::rbd::TRASH_IMAGE_SOURCE_MIRRORING, "name", {}, {}}); + m_threads->work_queue->drain(); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, CreateBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, -EBLACKLISTED); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, CreateDNE) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, -ENOENT); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(-ENOENT, ctx.wait()); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, CreateError) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, -EINVAL); + + expect_timer_add_event(mock_threads); + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, RegisterWatcherBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, -EBLACKLISTED); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, RegisterWatcherError) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, -EINVAL); + expect_timer_add_event(mock_threads); + + expect_create_trash(m_local_io_ctx, 0); + + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, TrashListBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + expect_trash_list(m_local_io_ctx, "", {}, -EBLACKLISTED); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, TrashListError) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + expect_trash_list(m_local_io_ctx, "", {}, -EINVAL); + + expect_timer_add_event(mock_threads); + expect_create_trash(m_local_io_ctx, 0); + + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, false); + expect_trash_list(m_local_io_ctx, "", {}, 0); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, Rewatch) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + expect_trash_list(m_local_io_ctx, "", {}, 0); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + expect_timer_add_event(mock_threads); + expect_create_trash(m_local_io_ctx, 0); + + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, false); + expect_trash_list(m_local_io_ctx, "", {}, 0); + LibrbdTrashWatcher::get_instance().handle_rewatch_complete(0); + m_threads->work_queue->drain(); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +TEST_F(TestMockImageDeleterTrashWatcher, RewatchBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + expect_create_trash(m_local_io_ctx, 0); + + MockLibrbdTrashWatcher mock_librbd_trash_watcher; + expect_trash_watcher_is_unregistered(mock_librbd_trash_watcher, true); + expect_trash_watcher_register(mock_librbd_trash_watcher, 0); + expect_trash_list(m_local_io_ctx, "", {}, 0); + + MockListener mock_listener; + MockTrashWatcher mock_trash_watcher(m_local_io_ctx, &mock_threads, + mock_listener); + C_SaferCond ctx; + mock_trash_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + LibrbdTrashWatcher::get_instance().handle_rewatch_complete(-EBLACKLISTED); + m_threads->work_queue->drain(); + + expect_trash_watcher_unregister(mock_librbd_trash_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_trash_watcher)); +} + +} // namespace image_deleter +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/image_map/test_Policy.cc b/src/test/rbd_mirror/image_map/test_Policy.cc new file mode 100644 index 00000000..4535ab7a --- /dev/null +++ b/src/test/rbd_mirror/image_map/test_Policy.cc @@ -0,0 +1,377 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/Context.h" +#include "test/rbd_mirror/test_fixture.h" +#include "tools/rbd_mirror/image_map/Types.h" +#include "tools/rbd_mirror/image_map/SimplePolicy.h" +#include "include/stringify.h" +#include "common/Thread.h" + +void register_test_image_policy() { +} + +namespace rbd { +namespace mirror { +namespace image_map { + +class TestImageMapPolicy : public TestFixture { +public: + void SetUp() override { + TestFixture::SetUp(); + + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_migration_throttle", + "0")); + + CephContext *cct = reinterpret_cast(m_local_io_ctx.cct()); + std::string policy_type = cct->_conf.get_val("rbd_mirror_image_policy_type"); + + if (policy_type == "none" || policy_type == "simple") { + m_policy = image_map::SimplePolicy::create(m_local_io_ctx); + } else { + ceph_abort(); + } + + m_policy->init({}); + } + + void TearDown() override { + TestFixture::TearDown(); + delete m_policy; + } + + void map_image(const std::string &global_image_id) { + ASSERT_TRUE(m_policy->add_image(global_image_id)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); + } + + void unmap_image(const std::string &global_image_id) { + ASSERT_TRUE(m_policy->remove_image(global_image_id)); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_REMOVE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); + } + + void shuffle_image(const std::string &global_image_id) { + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); + } + + Policy *m_policy; +}; + +TEST_F(TestImageMapPolicy, NegativeLookup) { + const std::string global_image_id = "global id 1"; + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id == UNMAPPED_INSTANCE_ID); +} + +TEST_F(TestImageMapPolicy, Init) { + const std::string global_image_id = "global id 1"; + + m_policy->init({{global_image_id, {"9876", {}, {}}}}); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); +} + +TEST_F(TestImageMapPolicy, MapImage) { + const std::string global_image_id = "global id 1"; + + map_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); +} + +TEST_F(TestImageMapPolicy, UnmapImage) { + const std::string global_image_id = "global id 1"; + + // map image + map_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + + // unmap image + unmap_image(global_image_id); + + info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id == UNMAPPED_INSTANCE_ID); +} + +TEST_F(TestImageMapPolicy, ShuffleImageAddInstance) { + std::set global_image_ids { + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5", "global id 6" + }; + + for (auto const &global_image_id : global_image_ids) { + // map image + map_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + } + + std::set shuffle_global_image_ids; + m_policy->add_instances({"9876"}, &shuffle_global_image_ids); + + for (auto const &global_image_id : shuffle_global_image_ids) { + shuffle_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + } +} + +TEST_F(TestImageMapPolicy, ShuffleImageRemoveInstance) { + std::set global_image_ids { + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5" + }; + + std::set shuffle_global_image_ids; + m_policy->add_instances({stringify(m_local_io_ctx.get_instance_id())}, + &shuffle_global_image_ids); + for (auto const &global_image_id : global_image_ids) { + // map image + map_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + } + + m_policy->add_instances({"9876"}, &shuffle_global_image_ids); + + for (auto const &global_image_id : shuffle_global_image_ids) { + shuffle_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + } + + // record which of the images got migrated to the new instance + std::set remapped_global_image_ids; + for (auto const &global_image_id: shuffle_global_image_ids) { + LookupInfo info = m_policy->lookup(global_image_id); + if (info.instance_id == "9876") { + remapped_global_image_ids.emplace(global_image_id); + } + } + + shuffle_global_image_ids.clear(); + m_policy->remove_instances({"9876"}, &shuffle_global_image_ids); + + ASSERT_TRUE(shuffle_global_image_ids == remapped_global_image_ids); + + for (auto const &global_image_id : shuffle_global_image_ids) { + shuffle_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + } +} + +TEST_F(TestImageMapPolicy, RetryMapUpdate) { + const std::string global_image_id = "global id 1"; + + ASSERT_TRUE(m_policy->add_image(global_image_id)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + // on-disk map update failed + ASSERT_TRUE(m_policy->finish_action(global_image_id, -EIO)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); +} + +TEST_F(TestImageMapPolicy, MapFailureAndUnmap) { + const std::string global_image_id = "global id 1"; + + ASSERT_TRUE(m_policy->add_image(global_image_id)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + + std::set shuffle_global_image_ids; + m_policy->add_instances({"9876"}, &shuffle_global_image_ids); + ASSERT_TRUE(shuffle_global_image_ids.empty()); + + m_policy->remove_instances({stringify(m_local_io_ctx.get_instance_id())}, + &shuffle_global_image_ids); + ASSERT_TRUE(shuffle_global_image_ids.empty()); + + ASSERT_TRUE(m_policy->finish_action(global_image_id, -EBLACKLISTED)); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, -ENOENT)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_TRUE(m_policy->remove_image(global_image_id)); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_REMOVE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); +} + +TEST_F(TestImageMapPolicy, ReshuffleWithMapFailure) { + std::set global_image_ids { + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5", + "global id 6" + }; + + std::set shuffle_global_image_ids; + m_policy->add_instances({stringify(m_local_io_ctx.get_instance_id())}, + &shuffle_global_image_ids); + for (auto const &global_image_id : global_image_ids) { + // map image + map_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + } + + m_policy->add_instances({"9876"}, &shuffle_global_image_ids); + ASSERT_FALSE(shuffle_global_image_ids.empty()); + + const std::string global_image_id = *(shuffle_global_image_ids.begin()); + shuffle_global_image_ids.clear(); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + + // peer unavailable + m_policy->remove_instances({"9876"}, &shuffle_global_image_ids); + ASSERT_TRUE(shuffle_global_image_ids.empty()); + + ASSERT_TRUE(m_policy->finish_action(global_image_id, -EBLACKLISTED)); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); +} + +TEST_F(TestImageMapPolicy, ShuffleFailureAndRemove) { + std::set global_image_ids { + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5", + "global id 6" + }; + + std::set shuffle_global_image_ids; + m_policy->add_instances({stringify(m_local_io_ctx.get_instance_id())}, + &shuffle_global_image_ids); + for (auto const &global_image_id : global_image_ids) { + // map image + map_image(global_image_id); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id != UNMAPPED_INSTANCE_ID); + } + + m_policy->add_instances({"9876"}, &shuffle_global_image_ids); + ASSERT_FALSE(shuffle_global_image_ids.empty()); + + std::string global_image_id = *(shuffle_global_image_ids.begin()); + shuffle_global_image_ids.clear(); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + + // peer unavailable + m_policy->remove_instances({"9876"}, &shuffle_global_image_ids); + ASSERT_TRUE(shuffle_global_image_ids.empty()); + + ASSERT_TRUE(m_policy->finish_action(global_image_id, -EBLACKLISTED)); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_TRUE(m_policy->remove_image(global_image_id)); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_REMOVE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); + + LookupInfo info = m_policy->lookup(global_image_id); + ASSERT_TRUE(info.instance_id == UNMAPPED_INSTANCE_ID); +} + +TEST_F(TestImageMapPolicy, InitialInstanceUpdate) { + const std::string global_image_id = "global id 1"; + + m_policy->init({{global_image_id, {"9876", {}, {}}}}); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + + auto instance_id = stringify(m_local_io_ctx.get_instance_id()); + std::set shuffle_global_image_ids; + m_policy->add_instances({instance_id}, &shuffle_global_image_ids); + + ASSERT_EQ(0U, shuffle_global_image_ids.size()); + ASSERT_TRUE(m_policy->finish_action(global_image_id, -ENOENT)); + + ASSERT_EQ(ACTION_TYPE_RELEASE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_MAP_UPDATE, m_policy->start_action(global_image_id)); + ASSERT_TRUE(m_policy->finish_action(global_image_id, 0)); + + ASSERT_EQ(ACTION_TYPE_ACQUIRE, m_policy->start_action(global_image_id)); + ASSERT_FALSE(m_policy->finish_action(global_image_id, 0)); +} + +} // namespace image_map +} // 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 00000000..6c7c30a2 --- /dev/null +++ b/src/test/rbd_mirror/image_replayer/test_mock_BootstrapRequest.cc @@ -0,0 +1,1195 @@ +// -*- 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/InstanceWatcher.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_replayer/BootstrapRequest.h" +#include "tools/rbd_mirror/image_replayer/CloseImageRequest.h" +#include "tools/rbd_mirror/image_replayer/CreateImageRequest.h" +#include "tools/rbd_mirror/image_replayer/IsPrimaryRequest.h" +#include "tools/rbd_mirror/image_replayer/OpenImageRequest.h" +#include "tools/rbd_mirror/image_replayer/OpenLocalImageRequest.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 journal { + +template <> +struct TypeTraits { + typedef ::journal::MockJournaler Journaler; +}; + +} // namespace journal + +namespace util { + +static std::string s_image_id; + +template <> +std::string generate_image_id(librados::IoCtx&) { + ceph_assert(!s_image_id.empty()); + return s_image_id; +} + +} // namespace util +} // namespace librbd + +namespace rbd { +namespace mirror { + +class ProgressContext; + +template <> +struct Threads { + Mutex &timer_lock; + SafeTimer *timer; + ContextWQ *work_queue; + + Threads(Threads *threads) + : timer_lock(threads->timer_lock), timer(threads->timer), + work_queue(threads->work_queue) { + } +}; + +template<> +struct ImageSync { + static ImageSync* s_instance; + Context *on_finish = nullptr; + + static ImageSync* create( + librbd::MockTestImageCtx *local_image_ctx, + librbd::MockTestImageCtx *remote_image_ctx, + SafeTimer *timer, Mutex *timer_lock, + const std::string &mirror_uuid, ::journal::MockJournaler *journaler, + librbd::journal::MirrorPeerClientMeta *client_meta, ContextWQ *work_queue, + InstanceWatcher *instance_watcher, + Context *on_finish, ProgressContext *progress_ctx) { + 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* + ImageSync::s_instance = nullptr; + +template<> +struct InstanceWatcher { +}; + +namespace image_replayer { + +template<> +struct CloseImageRequest { + 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; + s_instance->construct(*image_ctx); + 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 CreateImageRequest { + static CreateImageRequest* s_instance; + Context *on_finish = nullptr; + + static CreateImageRequest* create(Threads* 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, + 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()); +}; + +template<> +struct IsPrimaryRequest { + static IsPrimaryRequest* s_instance; + bool *primary = nullptr; + Context *on_finish = nullptr; + + static IsPrimaryRequest* create(librbd::MockTestImageCtx *image_ctx, + bool *primary, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->primary = primary; + s_instance->on_finish = on_finish; + return s_instance; + } + + IsPrimaryRequest() { + ceph_assert(s_instance == nullptr); + s_instance = this; + } + ~IsPrimaryRequest() { + s_instance = nullptr; + } + + MOCK_METHOD0(send, void()); +}; + +template<> +struct OpenImageRequest { + 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 { + 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, + 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()); +}; + +CloseImageRequest* + CloseImageRequest::s_instance = nullptr; +CreateImageRequest* + CreateImageRequest::s_instance = nullptr; +IsPrimaryRequest* + IsPrimaryRequest::s_instance = nullptr; +OpenImageRequest* + OpenImageRequest::s_instance = nullptr; +OpenLocalImageRequest* + OpenLocalImageRequest::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; + +MATCHER_P(IsSameIoCtx, io_ctx, "") { + return &get_mock_io_ctx(arg) == &get_mock_io_ctx(*io_ctx); +} + +class TestMockImageReplayerBootstrapRequest : public TestMockFixture { +public: + typedef Threads MockThreads; + typedef BootstrapRequest MockBootstrapRequest; + typedef CloseImageRequest MockCloseImageRequest; + typedef CreateImageRequest MockCreateImageRequest; + typedef ImageSync MockImageSync; + typedef InstanceWatcher MockInstanceWatcher; + typedef IsPrimaryRequest MockIsPrimaryRequest; + typedef OpenImageRequest MockOpenImageRequest; + typedef OpenLocalImageRequest MockOpenLocalImageRequest; + typedef std::list 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)); + } + + 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_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_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_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_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]() { + *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_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]() { + *mock_close_image_request.image_ctx = nullptr; + m_threads->work_queue->queue(mock_close_image_request.on_finish, r); + })); + } + + void expect_is_primary(MockIsPrimaryRequest &mock_is_primary_request, + bool primary, int r) { + EXPECT_CALL(mock_is_primary_request, send()) + .WillOnce(Invoke([this, &mock_is_primary_request, primary, r]() { + *mock_is_primary_request.primary = primary; + m_threads->work_queue->queue(mock_is_primary_request.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))); + } + + 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); + })); + } + + 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()); + } + + bufferlist encode_tag_data(const librbd::journal::TagData &tag_data) { + bufferlist bl; + encode(tag_data, bl); + return bl; + } + + MockBootstrapRequest *create_request(MockThreads* mock_threads, + MockInstanceWatcher *mock_instance_watcher, + ::journal::MockJournaler &mock_journaler, + const std::string &local_image_id, + const std::string &remote_image_id, + const std::string &global_image_id, + const std::string &local_mirror_uuid, + const std::string &remote_mirror_uuid, + cls::journal::ClientState *client_state, + librbd::journal::MirrorPeerClientMeta *mirror_peer_client_meta, + Context *on_finish) { + return new MockBootstrapRequest(mock_threads, m_local_io_ctx, + m_remote_io_ctx, + mock_instance_watcher, + &m_local_test_image_ctx, + local_image_id, + remote_image_id, + global_image_id, + local_mirror_uuid, + remote_mirror_uuid, + &mock_journaler, + client_state, mirror_peer_client_meta, + on_finish, &m_do_resync); + } + + librbd::ImageCtx *m_remote_image_ctx; + librbd::ImageCtx *m_local_image_ctx = nullptr; + librbd::MockTestImageCtx *m_local_test_image_ctx = nullptr; + bool m_do_resync; +}; + +TEST_F(TestMockImageReplayerBootstrapRequest, NonPrimaryRemoteSyncingState) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, false, 0); + + // switch the state to replaying + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{ + mock_local_image_ctx.id}; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + client_data.client_meta = mirror_peer_client_meta; + expect_journaler_update_client(mock_journaler, client_data, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, + mock_local_image_ctx.id, mock_remote_image_ctx.id, "global image id", + "local mirror uuid", "remote mirror uuid", &client_state, + &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(-EREMOTEIO, ctx.wait()); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, NonPrimaryRemoteNotTagOwner) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, false, 0); + + // open the local image + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + 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); + 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}); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_local_image_ctx, 0); + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{ + mock_local_image_ctx.id}; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, + mock_local_image_ctx.id, mock_remote_image_ctx.id, "global image id", + "local mirror uuid", "remote mirror uuid", &client_state, + &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(-EREMOTEIO, ctx.wait()); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, RemoteDemotePromote) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, false, 0); + + // open the local image + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + 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); + 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"}); + + // 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_journaler, 123, tags, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{ + mock_local_image_ctx.id}; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, + mock_local_image_ctx.id, mock_remote_image_ctx.id, "global image id", + "local mirror uuid", "remote mirror uuid", &client_state, + &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, MultipleRemoteDemotePromotes) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, true, 0); + + // open the local image + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + 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); + 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}); + + // 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_journaler, 123, tags, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{ + mock_local_image_ctx.id}; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, + mock_local_image_ctx.id, mock_remote_image_ctx.id, "global image id", + "local mirror uuid", "remote mirror uuid", &client_state, + &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, LocalDemoteRemotePromote) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, true, 0); + + // open the local image + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + 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); + 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}); + + // 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_journaler, 123, tags, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{ + mock_local_image_ctx.id}; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, + mock_local_image_ctx.id, mock_remote_image_ctx.id, "global image id", + "local mirror uuid", "remote mirror uuid", &client_state, + &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, SplitBrainForcePromote) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, true, 0); + + // open the local image + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + 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); + 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}); + + // 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_journaler, 123, tags, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_local_image_ctx, 0); + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{ + mock_local_image_ctx.id}; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, + mock_local_image_ctx.id, mock_remote_image_ctx.id, "global image id", + "local mirror uuid", "remote mirror uuid", &client_state, + &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(-EEXIST, ctx.wait()); + ASSERT_EQ(NULL, m_local_test_image_ctx); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, ResyncRequested) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, true, 0); + + // open the local image + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + 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); + + // resync is requested + 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"}); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta{ + mock_local_image_ctx.id}; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, + mock_local_image_ctx.id, mock_remote_image_ctx.id, "global image id", + "local mirror uuid", "remote mirror uuid", &client_state, + &mirror_peer_client_meta, &ctx); + m_do_resync = false; + request->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(m_do_resync); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, PrimaryRemote) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, true, 0); + + // update client state back to syncing + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + + librbd::util::s_image_id = mock_local_image_ctx.id; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta; + mirror_peer_client_meta.image_id = mock_local_image_ctx.id; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING; + client_data.client_meta = mirror_peer_client_meta; + client.data.clear(); + encode(client_data, client.data); + expect_journaler_update_client(mock_journaler, client_data, 0); + + // create the local image + MockCreateImageRequest mock_create_image_request; + expect_create_image(mock_create_image_request, mock_local_image_ctx.id, 0); + + // open the local image + 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); + 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"}); + + // sync the remote image to the local image + MockImageSync mock_image_sync; + expect_image_sync(mock_image_sync, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + mirror_peer_client_meta.image_id = ""; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, "", + mock_remote_image_ctx.id, "global image id", "local mirror uuid", + "remote mirror uuid", &client_state, &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, PrimaryRemoteLocalDeleted) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, true, 0); + + // open the missing local image + MockOpenLocalImageRequest mock_open_local_image_request; + expect_open_local_image(mock_open_local_image_request, m_local_io_ctx, + "missing image id", nullptr, -ENOENT); + + // re-register the client + expect_journaler_unregister_client(mock_journaler, 0); + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta; + mirror_peer_client_meta.image_id = ""; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + client_data.client_meta = mirror_peer_client_meta; + expect_journaler_register_client(mock_journaler, client_data, 0); + + // test if remote image is primary + expect_is_primary(mock_is_primary_request, true, 0); + + // update client state back to syncing + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + + librbd::util::s_image_id = mock_local_image_ctx.id; + mirror_peer_client_meta.image_id = mock_local_image_ctx.id; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING; + client_data.client_meta = mirror_peer_client_meta; + client.data.clear(); + encode(client_data, client.data); + expect_journaler_update_client(mock_journaler, client_data, 0); + + // create the missing local image + MockCreateImageRequest mock_create_image_request; + expect_create_image(mock_create_image_request, mock_local_image_ctx.id, 0); + + // open the local image + expect_open_local_image(mock_open_local_image_request, m_local_io_ctx, + mock_local_image_ctx.id, &mock_local_image_ctx, 0); + 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"}); + + // sync the remote image to the local image + MockImageSync mock_image_sync; + expect_image_sync(mock_image_sync, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + mirror_peer_client_meta.image_id = "missing image id"; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, "missing image id", + mock_remote_image_ctx.id, "global image id", "local mirror uuid", + "remote mirror uuid", &client_state, &mirror_peer_client_meta, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerBootstrapRequest, LocalImageIdCollision) { + create_local_image(); + + InSequence seq; + + // lookup remote image tag class + cls::journal::Client client; + librbd::journal::ClientData client_data{ + librbd::journal::ImageClientMeta{123}}; + encode(client_data, client.data); + ::journal::MockJournaler mock_journaler; + expect_journaler_get_client(mock_journaler, + librbd::Journal<>::IMAGE_CLIENT_ID, + client, 0); + + // open the remote image + librbd::MockJournal mock_journal; + 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); + + // test if remote image is primary + MockIsPrimaryRequest mock_is_primary_request; + expect_is_primary(mock_is_primary_request, true, 0); + + // update client state back to syncing + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + mock_local_image_ctx.journal = &mock_journal; + + librbd::util::s_image_id = mock_local_image_ctx.id; + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta; + mirror_peer_client_meta.image_id = mock_local_image_ctx.id; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_SYNCING; + client_data.client_meta = mirror_peer_client_meta; + client.data.clear(); + encode(client_data, client.data); + expect_journaler_update_client(mock_journaler, client_data, 0); + + // create the local image + MockCreateImageRequest mock_create_image_request; + expect_create_image(mock_create_image_request, mock_local_image_ctx.id, + -EBADF); + + expect_journaler_update_client(mock_journaler, client_data, 0); + expect_create_image(mock_create_image_request, mock_local_image_ctx.id, 0); + + // open the local image + 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); + 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"}); + + // sync the remote image to the local image + MockImageSync mock_image_sync; + expect_image_sync(mock_image_sync, 0); + + MockCloseImageRequest mock_close_image_request; + expect_close_image(mock_close_image_request, mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockThreads mock_threads(m_threads); + MockInstanceWatcher mock_instance_watcher; + cls::journal::ClientState client_state = cls::journal::CLIENT_STATE_CONNECTED; + mirror_peer_client_meta.image_id = ""; + mirror_peer_client_meta.state = librbd::journal::MIRROR_PEER_STATE_REPLAYING; + MockBootstrapRequest *request = create_request( + &mock_threads, &mock_instance_watcher, mock_journaler, "", + mock_remote_image_ctx.id, "global image id", "local mirror uuid", + "remote mirror uuid", &client_state, &mirror_peer_client_meta, &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 00000000..5c446014 --- /dev/null +++ b/src/test/rbd_mirror/image_replayer/test_mock_CreateImageRequest.cc @@ -0,0 +1,758 @@ +// -*- 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/journal/mock/MockJournaler.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/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 { + 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, + const std::string &non_primary_global_image_id, + const std::string &primary_mirror_uuid, + bool skip_mirror_enable, + 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* + CreateRequest::s_instance = nullptr; + +template<> +struct CloneRequest { + 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, + uint64_t p_snap_id, + IoCtx &c_ioctx, const std::string &c_name, + const std::string &c_id, ImageOptions c_options, + 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* + CloneRequest::s_instance = nullptr; + +} // namespace image + +namespace journal { + +template <> +struct TypeTraits { + typedef ::journal::MockJournalerProxy Journaler; +}; + +} // namespace journal +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct Threads { + Mutex &timer_lock; + SafeTimer *timer; + ContextWQ *work_queue; + + Threads(Threads *threads) + : timer_lock(threads->timer_lock), timer(threads->timer), + work_queue(threads->work_queue) { + } +}; + +namespace image_replayer { + +template<> +struct CloseImageRequest { + 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 { + 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* + CloseImageRequest::s_instance = nullptr; +OpenImageRequest* + OpenImageRequest::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; + +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 MockThreads; + typedef librbd::image::CreateRequest MockCreateRequest; + typedef librbd::image::CloneRequest MockCloneRequest; + typedef CreateImageRequest MockCreateImageRequest; + typedef OpenImageRequest MockOpenImageRequest; + typedef CloseImageRequest 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) { + ASSERT_EQ(0, image_ctx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str())); + ASSERT_EQ(0, image_ctx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str())); + 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); + })); + } + + void expect_mirror_uuid_get(librados::IoCtx &io_ctx, + const std::string &mirror_uuid, int r) { + bufferlist bl; + encode(mirror_uuid, bl); + + EXPECT_CALL(get_mock_io_ctx(io_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_uuid_get"), _, _, _)) + .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) { + *out_bl = bl; + })), + Return(r))); + } + + void expect_journaler_get_client(journal::MockJournaler& mock_journaler, + const std::string& client_id, + librbd::journal::MirrorPeerState state, + int r) { + EXPECT_CALL(mock_journaler, construct()); + + librbd::journal::MirrorPeerClientMeta mirror_peer_client_meta; + mirror_peer_client_meta.state = state; + + librbd::journal::ClientData client_data{mirror_peer_client_meta}; + + cls::journal::Client client; + encode(client_data, client.data); + + 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); + })))); + } + + 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, on_finish); + } + + 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, Clone) { + 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)); + + 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; + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_REPLAYING, 0); + 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, 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(0, ctx.wait()); +} + +TEST_F(TestMockImageReplayerCreateImageRequest, CloneParentMirrorUuidGetError) { + 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_mirror_uuid_get(m_local_io_ctx, "local parent uuid", -EPERM); + + 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(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageReplayerCreateImageRequest, CloneGetRemoteParentClientStateError) { + 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); + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_REPLAYING, -EPERM); + + 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(-EPERM, ctx.wait()); +} + +TEST_F(TestMockImageReplayerCreateImageRequest, CloneGetRemoteParentClientStateSyncing) { + 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); + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_SYNCING, 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, 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); + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_REPLAYING, 0); + 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); + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_REPLAYING, 0); + 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; + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_REPLAYING, 0); + 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, 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)); + + 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; + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_REPLAYING, 0); + 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)); + + 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; + journal::MockJournaler mock_remote_journaler; + + InSequence seq; + expect_ioctx_create(m_remote_io_ctx); + expect_ioctx_create(m_local_io_ctx); + expect_mirror_uuid_get(m_local_io_ctx, "local parent uuid", 0); + expect_journaler_get_client( + mock_remote_journaler, "local parent uuid", + librbd::journal::MIRROR_PEER_STATE_REPLAYING, 0); + 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_EventPreprocessor.cc b/src/test/rbd_mirror/image_replayer/test_mock_EventPreprocessor.cc new file mode 100644 index 00000000..9e02a82f --- /dev/null +++ b/src/test/rbd_mirror/image_replayer/test_mock_EventPreprocessor.cc @@ -0,0 +1,265 @@ +// -*- 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/image_replayer/EventPreprocessor.h" +#include "tools/rbd_mirror/Threads.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 { + typedef ::journal::MockJournaler Journaler; +}; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "tools/rbd_mirror/image_replayer/EventPreprocessor.cc" +template class rbd::mirror::image_replayer::EventPreprocessor; + +namespace rbd { +namespace mirror { +namespace image_replayer { + +using testing::_; +using testing::WithArg; + +class TestMockImageReplayerEventPreprocessor : public TestMockFixture { +public: + typedef EventPreprocessor 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(TestMockImageReplayerEventPreprocessor, 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(TestMockImageReplayerEventPreprocessor, 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(TestMockImageReplayerEventPreprocessor, 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(TestMockImageReplayerEventPreprocessor, 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(TestMockImageReplayerEventPreprocessor, 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(&event_entry.event); + ASSERT_EQ(6U, event->snap_id); +} + +TEST_F(TestMockImageReplayerEventPreprocessor, 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(&event_entry.event); + ASSERT_EQ(CEPH_NOSNAP, event->snap_id); +} + +TEST_F(TestMockImageReplayerEventPreprocessor, 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(&event_entry.event); + ASSERT_EQ(6U, event->snap_id); +} + +TEST_F(TestMockImageReplayerEventPreprocessor, 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(TestMockImageReplayerEventPreprocessor, 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 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 00000000..40007dfd --- /dev/null +++ b/src/test/rbd_mirror/image_replayer/test_mock_GetMirrorImageIdRequest.cc @@ -0,0 +1,106 @@ +// -*- 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 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 00000000..64e0697e --- /dev/null +++ b/src/test/rbd_mirror/image_replayer/test_mock_PrepareLocalImageRequest.cc @@ -0,0 +1,264 @@ +// -*- 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 "tools/rbd_mirror/image_replayer/PrepareLocalImageRequest.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 + +namespace rbd { +namespace mirror { +namespace image_replayer { + +template <> +struct GetMirrorImageIdRequest { + 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()); +}; + +GetMirrorImageIdRequest* GetMirrorImageIdRequest::s_instance = nullptr; + +} // 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 PrepareLocalImageRequest MockPrepareLocalImageRequest; + typedef GetMirrorImageIdRequest MockGetMirrorImageIdRequest; + + 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_mirror_image_get(librados::IoCtx &io_ctx, + cls::rbd::MirrorImageState state, + const std::string &global_id, int r) { + cls::rbd::MirrorImage mirror_image; + mirror_image.state = state; + 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_get_tag_owner(librbd::MockJournal &mock_journal, + const std::string &local_image_id, + const std::string &tag_owner, int r) { + EXPECT_CALL(mock_journal, get_tag_owner(local_image_id, _, _, _)) + .WillOnce(WithArgs<1, 3>(Invoke([tag_owner, r](std::string *owner, Context *on_finish) { + *owner = tag_owner; + on_finish->complete(r); + }))); + } + +}; + +TEST_F(TestMockImageReplayerPrepareLocalImageRequest, Success) { + 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); + expect_mirror_image_get(m_local_io_ctx, cls::rbd::MIRROR_IMAGE_STATE_ENABLED, + "global image id", 0); + + librbd::MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "local image id", "remote mirror uuid", 0); + + std::string local_image_id; + std::string local_image_name; + std::string tag_owner; + C_SaferCond ctx; + auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx, + "global image id", + &local_image_id, + &local_image_name, + &tag_owner, + m_threads->work_queue, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(std::string("local image id"), local_image_id); + ASSERT_EQ(std::string("local image name"), local_image_name); + ASSERT_EQ(std::string("remote mirror uuid"), tag_owner); +} + +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); + + std::string local_image_id; + std::string local_image_name; + std::string tag_owner; + C_SaferCond ctx; + auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx, + "global image id", + &local_image_id, + &local_image_name, + &tag_owner, + m_threads->work_queue, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, 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, "", -ENOENT); + + std::string local_image_id; + std::string local_image_name; + std::string tag_owner; + C_SaferCond ctx; + auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx, + "global image id", + &local_image_id, + &local_image_name, + &tag_owner, + m_threads->work_queue, + &ctx); + req->send(); + + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockImageReplayerPrepareLocalImageRequest, MirrorImageError) { + 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); + expect_mirror_image_get(m_local_io_ctx, cls::rbd::MIRROR_IMAGE_STATE_DISABLED, + "", -EINVAL); + + std::string local_image_id; + std::string local_image_name; + std::string tag_owner; + C_SaferCond ctx; + auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx, + "global image id", + &local_image_id, + &local_image_name, + &tag_owner, + m_threads->work_queue, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageReplayerPrepareLocalImageRequest, TagOwnerError) { + 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); + expect_mirror_image_get(m_local_io_ctx, cls::rbd::MIRROR_IMAGE_STATE_ENABLED, + "global image id", 0); + + librbd::MockJournal mock_journal; + expect_get_tag_owner(mock_journal, "local image id", "remote mirror uuid", + -ENOENT); + + std::string local_image_id; + std::string local_image_name; + std::string tag_owner; + C_SaferCond ctx; + auto req = MockPrepareLocalImageRequest::create(m_local_io_ctx, + "global image id", + &local_image_id, + &local_image_name, + &tag_owner, + m_threads->work_queue, + &ctx); + req->send(); + + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +} // 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 00000000..1b957ed1 --- /dev/null +++ b/src/test/rbd_mirror/image_replayer/test_mock_PrepareRemoteImageRequest.cc @@ -0,0 +1,396 @@ +// -*- 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/Threads.h" +#include "tools/rbd_mirror/image_replayer/GetMirrorImageIdRequest.h" +#include "tools/rbd_mirror/image_replayer/PrepareRemoteImageRequest.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 { + typedef ::journal::MockJournalerProxy Journaler; +}; + +} // namespace journal +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct Threads { + Mutex &timer_lock; + SafeTimer *timer; + ContextWQ *work_queue; + + Threads(Threads *threads) + : timer_lock(threads->timer_lock), timer(threads->timer), + work_queue(threads->work_queue) { + } +}; + +namespace image_replayer { + +template <> +struct GetMirrorImageIdRequest { + 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()); +}; + +GetMirrorImageIdRequest* GetMirrorImageIdRequest::s_instance = nullptr; + +} // 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 MockThreads; + typedef PrepareRemoteImageRequest MockPrepareRemoteImageRequest; + typedef GetMirrorImageIdRequest MockGetMirrorImageIdRequest; + + 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_mirror_uuid_get(librados::IoCtx &io_ctx, + const std::string &mirror_uuid, int r) { + bufferlist bl; + encode(mirror_uuid, bl); + + EXPECT_CALL(get_mock_io_ctx(io_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_uuid_get"), _, _, _)) + .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) { + *out_bl = bl; + })), + Return(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, Success) { + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + + InSequence seq; + expect_mirror_uuid_get(m_remote_io_ctx, "remote mirror uuid", 0); + MockGetMirrorImageIdRequest mock_get_mirror_image_id_request; + expect_get_mirror_image_id(mock_get_mirror_image_id_request, + "remote image id", 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); + + std::string remote_mirror_uuid; + std::string remote_image_id; + journal::MockJournalerProxy *remote_journaler = nullptr; + cls::journal::ClientState client_state; + librbd::journal::MirrorPeerClientMeta client_meta; + C_SaferCond ctx; + auto req = MockPrepareRemoteImageRequest::create(&mock_threads, + m_remote_io_ctx, + "global image id", + "local mirror uuid", + "local image id", {}, + &remote_mirror_uuid, + &remote_image_id, + &remote_journaler, + &client_state, &client_meta, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(std::string("remote mirror uuid"), remote_mirror_uuid); + ASSERT_EQ(std::string("remote image id"), remote_image_id); + ASSERT_TRUE(remote_journaler != nullptr); + ASSERT_EQ(cls::journal::CLIENT_STATE_DISCONNECTED, client_state); + delete remote_journaler; +} + +TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, SuccessNotRegistered) { + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + + InSequence seq; + expect_mirror_uuid_get(m_remote_io_ctx, "remote mirror uuid", 0); + MockGetMirrorImageIdRequest mock_get_mirror_image_id_request; + expect_get_mirror_image_id(mock_get_mirror_image_id_request, + "remote image id", 0); + + 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); + + std::string remote_mirror_uuid; + std::string remote_image_id; + journal::MockJournalerProxy *remote_journaler = nullptr; + cls::journal::ClientState client_state; + librbd::journal::MirrorPeerClientMeta client_meta; + C_SaferCond ctx; + auto req = MockPrepareRemoteImageRequest::create(&mock_threads, + m_remote_io_ctx, + "global image id", + "local mirror uuid", + "local image id", {}, + &remote_mirror_uuid, + &remote_image_id, + &remote_journaler, + &client_state, &client_meta, + &ctx); + req->send(); + + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(std::string("remote mirror uuid"), remote_mirror_uuid); + ASSERT_EQ(std::string("remote image id"), remote_image_id); + ASSERT_TRUE(remote_journaler != nullptr); + ASSERT_EQ(cls::journal::CLIENT_STATE_CONNECTED, client_state); + delete remote_journaler; +} + +TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorUuidError) { + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + + InSequence seq; + expect_mirror_uuid_get(m_remote_io_ctx, "", -EINVAL); + + std::string remote_mirror_uuid; + std::string remote_image_id; + journal::MockJournalerProxy *remote_journaler = nullptr; + cls::journal::ClientState client_state; + librbd::journal::MirrorPeerClientMeta client_meta; + C_SaferCond ctx; + auto req = MockPrepareRemoteImageRequest::create(&mock_threads, + m_remote_io_ctx, + "global image id", + "local mirror uuid", + "", {}, + &remote_mirror_uuid, + &remote_image_id, + &remote_journaler, + &client_state, &client_meta, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); + ASSERT_EQ(std::string(""), remote_mirror_uuid); + ASSERT_TRUE(remote_journaler == nullptr); +} + +TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, MirrorImageIdError) { + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + + InSequence seq; + expect_mirror_uuid_get(m_remote_io_ctx, "remote mirror uuid", 0); + MockGetMirrorImageIdRequest mock_get_mirror_image_id_request; + expect_get_mirror_image_id(mock_get_mirror_image_id_request, "", -EINVAL); + + std::string remote_mirror_uuid; + std::string remote_image_id; + journal::MockJournalerProxy *remote_journaler = nullptr; + cls::journal::ClientState client_state; + librbd::journal::MirrorPeerClientMeta client_meta; + C_SaferCond ctx; + auto req = MockPrepareRemoteImageRequest::create(&mock_threads, + m_remote_io_ctx, + "global image id", + "local mirror uuid", + "", {}, + &remote_mirror_uuid, + &remote_image_id, + &remote_journaler, + &client_state, &client_meta, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); + ASSERT_EQ(std::string("remote mirror uuid"), remote_mirror_uuid); + ASSERT_TRUE(remote_journaler == nullptr); +} + +TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, GetClientError) { + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + + InSequence seq; + expect_mirror_uuid_get(m_remote_io_ctx, "remote mirror uuid", 0); + MockGetMirrorImageIdRequest mock_get_mirror_image_id_request; + expect_get_mirror_image_id(mock_get_mirror_image_id_request, + "remote image id", 0); + + EXPECT_CALL(mock_remote_journaler, construct()); + + cls::journal::Client client; + expect_journaler_get_client(mock_remote_journaler, "local mirror uuid", + client, -EINVAL); + + std::string remote_mirror_uuid; + std::string remote_image_id; + journal::MockJournalerProxy *remote_journaler = nullptr; + cls::journal::ClientState client_state; + librbd::journal::MirrorPeerClientMeta client_meta; + C_SaferCond ctx; + auto req = MockPrepareRemoteImageRequest::create(&mock_threads, + m_remote_io_ctx, + "global image id", + "local mirror uuid", + "local image id", {}, + &remote_mirror_uuid, + &remote_image_id, + &remote_journaler, + &client_state, &client_meta, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); + ASSERT_EQ(std::string("remote mirror uuid"), remote_mirror_uuid); + ASSERT_EQ(std::string("remote image id"), remote_image_id); + ASSERT_TRUE(remote_journaler == nullptr); +} + +TEST_F(TestMockImageReplayerPrepareRemoteImageRequest, RegisterClientError) { + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + + InSequence seq; + expect_mirror_uuid_get(m_remote_io_ctx, "remote mirror uuid", 0); + MockGetMirrorImageIdRequest mock_get_mirror_image_id_request; + expect_get_mirror_image_id(mock_get_mirror_image_id_request, + "remote image id", 0); + + 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); + + std::string remote_mirror_uuid; + std::string remote_image_id; + journal::MockJournalerProxy *remote_journaler = nullptr; + cls::journal::ClientState client_state; + librbd::journal::MirrorPeerClientMeta client_meta; + C_SaferCond ctx; + auto req = MockPrepareRemoteImageRequest::create(&mock_threads, + m_remote_io_ctx, + "global image id", + "local mirror uuid", + "local image id", {}, + &remote_mirror_uuid, + &remote_image_id, + &remote_journaler, + &client_state, &client_meta, + &ctx); + req->send(); + + ASSERT_EQ(-EINVAL, ctx.wait()); + ASSERT_EQ(std::string("remote mirror uuid"), remote_mirror_uuid); + ASSERT_EQ(std::string("remote image id"), remote_image_id); + ASSERT_TRUE(remote_journaler == nullptr); +} + +} // namespace image_replayer +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc new file mode 100644 index 00000000..37556257 --- /dev/null +++ b/src/test/rbd_mirror/image_sync/test_mock_SyncPointCreateRequest.cc @@ -0,0 +1,171 @@ +// -*- 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/journal/Types.h" +#include "librbd/journal/TypeTraits.h" +#include "test/journal/mock/MockJournaler.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.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 { + typedef ::journal::MockJournaler Journaler; +}; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.cc" +template class rbd::mirror::image_sync::SyncPointCreateRequest; + +namespace rbd { +namespace mirror { +namespace image_sync { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::WithArg; + +class TestMockImageSyncSyncPointCreateRequest : public TestMockFixture { +public: + typedef SyncPointCreateRequest MockSyncPointCreateRequest; + + 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 expect_update_client(journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, update_client(_, _)) + .WillOnce(WithArg<1>(CompleteContext(r))); + } + + 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_snap_create(librbd::MockTestImageCtx &mock_remote_image_ctx, int r) { + EXPECT_CALL(*mock_remote_image_ctx.operations, snap_create(_, _, _)) + .WillOnce(WithArg<2>(CompleteContext(r))); + } + + MockSyncPointCreateRequest *create_request(librbd::MockTestImageCtx &mock_remote_image_ctx, + journal::MockJournaler &mock_journaler, + Context *ctx) { + return new MockSyncPointCreateRequest(&mock_remote_image_ctx, "uuid", + &mock_journaler, &m_client_meta, ctx); + } + + librbd::ImageCtx *m_remote_image_ctx; + librbd::journal::MirrorPeerClientMeta m_client_meta; +}; + +TEST_F(TestMockImageSyncSyncPointCreateRequest, Success) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_update_client(mock_journaler, 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_snap_create(mock_remote_image_ctx, 0); + expect_image_refresh(mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockSyncPointCreateRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(1U, m_client_meta.sync_points.size()); +} + +TEST_F(TestMockImageSyncSyncPointCreateRequest, ResyncSuccess) { + m_client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "start snap", + "", + boost::none); + auto sync_point = m_client_meta.sync_points.front(); + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_update_client(mock_journaler, 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_snap_create(mock_remote_image_ctx, 0); + expect_image_refresh(mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockSyncPointCreateRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(2U, m_client_meta.sync_points.size()); + ASSERT_EQ(sync_point, m_client_meta.sync_points.front()); + ASSERT_EQ("start snap", m_client_meta.sync_points.back().from_snap_name); +} + +TEST_F(TestMockImageSyncSyncPointCreateRequest, SnapshotExists) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_update_client(mock_journaler, 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_snap_create(mock_remote_image_ctx, -EEXIST); + expect_update_client(mock_journaler, 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_snap_create(mock_remote_image_ctx, 0); + expect_image_refresh(mock_remote_image_ctx, 0); + + C_SaferCond ctx; + MockSyncPointCreateRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(1U, m_client_meta.sync_points.size()); +} + +TEST_F(TestMockImageSyncSyncPointCreateRequest, ClientUpdateError) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_update_client(mock_journaler, -EINVAL); + + C_SaferCond ctx; + MockSyncPointCreateRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); + + ASSERT_TRUE(m_client_meta.sync_points.empty()); +} + +} // namespace image_sync +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc b/src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc new file mode 100644 index 00000000..d230944e --- /dev/null +++ b/src/test/rbd_mirror/image_sync/test_mock_SyncPointPruneRequest.cc @@ -0,0 +1,338 @@ +// -*- 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/journal/Types.h" +#include "librbd/journal/TypeTraits.h" +#include "test/journal/mock/MockJournaler.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.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 { + typedef ::journal::MockJournaler Journaler; +}; + +} // namespace journal +} // namespace librbd + +// template definitions +#include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.cc" +template class rbd::mirror::image_sync::SyncPointPruneRequest; + +namespace rbd { +namespace mirror { +namespace image_sync { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageSyncSyncPointPruneRequest : public TestMockFixture { +public: + typedef SyncPointPruneRequest MockSyncPointPruneRequest; + + 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 expect_update_client(journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, update_client(_, _)) + .WillOnce(WithArg<1>(CompleteContext(r))); + } + + void expect_get_snap_id(librbd::MockTestImageCtx &mock_remote_image_ctx, + const std::string &snap_name, uint64_t snap_id) { + EXPECT_CALL(mock_remote_image_ctx, get_snap_id(_, StrEq(snap_name))) + .WillOnce(Return(snap_id)); + } + + 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_snap_remove(librbd::MockTestImageCtx &mock_remote_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_remote_image_ctx.operations, snap_remove(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(CompleteContext(r))); + } + + MockSyncPointPruneRequest *create_request(librbd::MockTestImageCtx &mock_remote_image_ctx, + journal::MockJournaler &mock_journaler, + bool sync_complete, Context *ctx) { + return new MockSyncPointPruneRequest(&mock_remote_image_ctx, sync_complete, + &mock_journaler, &m_client_meta, ctx); + } + + librbd::ImageCtx *m_remote_image_ctx; + librbd::journal::MirrorPeerClientMeta m_client_meta; +}; + +TEST_F(TestMockImageSyncSyncPointPruneRequest, SyncInProgressSuccess) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_get_snap_id(mock_remote_image_ctx, "snap1", 123); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_EQ(client_meta, m_client_meta); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, RestartedSyncInProgressSuccess) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap2", + "snap1", boost::none); + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_get_snap_id(mock_remote_image_ctx, "snap1", 123); + expect_snap_remove(mock_remote_image_ctx, "snap2", 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + client_meta.sync_points.pop_back(); + ASSERT_EQ(client_meta, m_client_meta); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, SyncInProgressMissingSnapSuccess) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap2", + "snap1", + boost::none); + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_get_snap_id(mock_remote_image_ctx, "snap1", CEPH_NOSNAP); + expect_snap_remove(mock_remote_image_ctx, "snap2", 0); + expect_snap_remove(mock_remote_image_ctx, "snap1", 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + client_meta.sync_points.clear(); + ASSERT_EQ(client_meta, m_client_meta); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, SyncInProgressUnexpectedFromSnapSuccess) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap2", + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_get_snap_id(mock_remote_image_ctx, "snap2", 124); + expect_snap_remove(mock_remote_image_ctx, "snap2", 0); + expect_snap_remove(mock_remote_image_ctx, "snap1", 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + client_meta.sync_points.clear(); + ASSERT_EQ(client_meta, m_client_meta); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, SyncCompleteSuccess) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + m_client_meta = client_meta; + ASSERT_EQ(librbd::journal::MIRROR_PEER_STATE_SYNCING, m_client_meta.state); + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_snap_remove(mock_remote_image_ctx, "snap1", 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(m_client_meta.sync_points.empty()); + ASSERT_EQ(librbd::journal::MIRROR_PEER_STATE_REPLAYING, m_client_meta.state); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, RestartedSyncCompleteSuccess) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap2", + "snap1", + boost::none); + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + client_meta.sync_points.pop_front(); + ASSERT_EQ(client_meta, m_client_meta); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, RestartedCatchUpSyncCompleteSuccess) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap3", + "snap2", + boost::none); + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap2", + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_snap_remove(mock_remote_image_ctx, "snap1", 0); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + client_meta.sync_points.pop_front(); + ASSERT_EQ(client_meta, m_client_meta); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, SnapshotDNE) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_snap_remove(mock_remote_image_ctx, "snap1", -ENOENT); + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, 0); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(m_client_meta.sync_points.empty()); +} + +TEST_F(TestMockImageSyncSyncPointPruneRequest, ClientUpdateError) { + librbd::journal::MirrorPeerClientMeta client_meta; + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap2", + "snap1", + boost::none); + client_meta.sync_points.emplace_front(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + m_client_meta = client_meta; + + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + journal::MockJournaler mock_journaler; + + InSequence seq; + expect_image_refresh(mock_remote_image_ctx, 0); + expect_update_client(mock_journaler, -EINVAL); + + C_SaferCond ctx; + MockSyncPointPruneRequest *req = create_request(mock_remote_image_ctx, + mock_journaler, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); + + ASSERT_EQ(client_meta, m_client_meta); +} + +} // namespace image_sync +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/mock/MockContextWQ.h b/src/test/rbd_mirror/mock/MockContextWQ.h new file mode 100644 index 00000000..1c0ee88f --- /dev/null +++ b/src/test/rbd_mirror/mock/MockContextWQ.h @@ -0,0 +1,18 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_MOCK_CONTEXT_WQ_H +#define CEPH_MOCK_CONTEXT_WQ_H + +#include + +struct Context; + +struct MockContextWQ { + void queue(Context *ctx) { + queue(ctx, 0); + } + MOCK_METHOD2(queue, void(Context *, int)); +}; + +#endif // CEPH_MOCK_CONTEXT_WQ_H diff --git a/src/test/rbd_mirror/mock/MockSafeTimer.h b/src/test/rbd_mirror/mock/MockSafeTimer.h new file mode 100644 index 00000000..32d58471 --- /dev/null +++ b/src/test/rbd_mirror/mock/MockSafeTimer.h @@ -0,0 +1,16 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_MOCK_SAFE_TIMER_H +#define CEPH_MOCK_SAFE_TIMER_H + +#include + +struct Context; + +struct MockSafeTimer { + MOCK_METHOD2(add_event_after, Context*(double, Context*)); + MOCK_METHOD1(cancel_event, bool(Context *)); +}; + +#endif // CEPH_MOCK_SAFE_TIMER_H diff --git a/src/test/rbd_mirror/pool_watcher/test_mock_RefreshImagesRequest.cc b/src/test/rbd_mirror/pool_watcher/test_mock_RefreshImagesRequest.cc new file mode 100644 index 00000000..afabcdfc --- /dev/null +++ b/src/test/rbd_mirror/pool_watcher/test_mock_RefreshImagesRequest.cc @@ -0,0 +1,116 @@ +// -*- 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 "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "tools/rbd_mirror/pool_watcher/RefreshImagesRequest.h" +#include "include/stringify.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace +} // namespace librbd + +// template definitions +#include "tools/rbd_mirror/pool_watcher/RefreshImagesRequest.cc" +template class rbd::mirror::pool_watcher::RefreshImagesRequest; + +namespace rbd { +namespace mirror { +namespace pool_watcher { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockPoolWatcherRefreshImagesRequest : public TestMockFixture { +public: + typedef RefreshImagesRequest MockRefreshImagesRequest; + + void expect_mirror_image_list(librados::IoCtx &io_ctx, + const std::map &ids, + int r) { + bufferlist bl; + encode(ids, bl); + + EXPECT_CALL(get_mock_io_ctx(io_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_list"), _, _, _)) + .WillOnce(DoAll(WithArg<5>(Invoke([bl](bufferlist *out_bl) { + *out_bl = bl; + })), + Return(r))); + } + +}; + +TEST_F(TestMockPoolWatcherRefreshImagesRequest, Success) { + InSequence seq; + expect_mirror_image_list(m_remote_io_ctx, {{"local id", "global id"}}, 0); + + C_SaferCond ctx; + ImageIds image_ids; + MockRefreshImagesRequest *req = new MockRefreshImagesRequest( + m_remote_io_ctx, &image_ids, &ctx); + + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ImageIds expected_image_ids = {{"global id", "local id"}}; + ASSERT_EQ(expected_image_ids, image_ids); +} + +TEST_F(TestMockPoolWatcherRefreshImagesRequest, LargeDirectory) { + InSequence seq; + std::map mirror_list; + ImageIds expected_image_ids; + for (uint32_t idx = 1; idx <= 1024; ++idx) { + mirror_list.insert(std::make_pair("local id " + stringify(idx), + "global id " + stringify(idx))); + expected_image_ids.insert({{"global id " + stringify(idx), + "local id " + stringify(idx)}}); + } + + expect_mirror_image_list(m_remote_io_ctx, mirror_list, 0); + expect_mirror_image_list(m_remote_io_ctx, {{"local id", "global id"}}, 0); + + C_SaferCond ctx; + ImageIds image_ids; + MockRefreshImagesRequest *req = new MockRefreshImagesRequest( + m_remote_io_ctx, &image_ids, &ctx); + + req->send(); + ASSERT_EQ(0, ctx.wait()); + + expected_image_ids.insert({"global id", "local id"}); + ASSERT_EQ(expected_image_ids, image_ids); +} + +TEST_F(TestMockPoolWatcherRefreshImagesRequest, MirrorImageListError) { + InSequence seq; + expect_mirror_image_list(m_remote_io_ctx, {}, -EINVAL); + + C_SaferCond ctx; + ImageIds image_ids; + MockRefreshImagesRequest *req = new MockRefreshImagesRequest( + m_remote_io_ctx, &image_ids, &ctx); + + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace pool_watcher +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/random_write.cc b/src/test/rbd_mirror/random_write.cc new file mode 100644 index 00000000..8a5423f0 --- /dev/null +++ b/src/test/rbd_mirror/random_write.cc @@ -0,0 +1,213 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "common/ceph_argparse.h" +#include "common/config.h" +#include "common/debug.h" +#include "common/errno.h" +#include "common/Cond.h" +#include "include/rados/librados.hpp" +#include "include/rbd/librbd.hpp" +#include "global/global_init.h" +#include +#include + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_rbd_mirror +#undef dout_prefix +#define dout_prefix *_dout << "random-write: " + +namespace { + +const uint32_t NUM_THREADS = 8; +const uint32_t MAX_IO_SIZE = 24576; +const uint32_t MIN_IO_SIZE = 4; + +void usage() { + std::cout << "usage: ceph_test_rbd_mirror_random_write [options...] \\" << std::endl; + std::cout << " " << std::endl; + std::cout << std::endl; + std::cout << " pool image pool" << std::endl; + std::cout << " image image to write" << std::endl; + std::cout << std::endl; + std::cout << "options:\n"; + std::cout << " -m monaddress[:port] connect to specified monitor\n"; + std::cout << " --keyring= path to keyring for local cluster\n"; + std::cout << " --log-file= file to log debug output\n"; + std::cout << " --debug-rbd-mirror=/ set rbd-mirror debug level\n"; + generic_server_usage(); +} + +void rbd_bencher_completion(void *c, void *pc); + +struct rbd_bencher { + librbd::Image *image; + Mutex lock; + Cond cond; + int in_flight; + + explicit rbd_bencher(librbd::Image *i) + : image(i), + lock("rbd_bencher::lock"), + in_flight(0) { + } + + bool start_write(int max, uint64_t off, uint64_t len, bufferlist& bl, + int op_flags) { + { + Mutex::Locker l(lock); + if (in_flight >= max) + return false; + in_flight++; + } + librbd::RBD::AioCompletion *c = + new librbd::RBD::AioCompletion((void *)this, rbd_bencher_completion); + image->aio_write2(off, len, bl, c, op_flags); + //cout << "start " << c << " at " << off << "~" << len << std::endl; + return true; + } + + void wait_for(int max) { + Mutex::Locker l(lock); + while (in_flight > max) { + utime_t dur; + dur.set_from_double(.2); + cond.WaitInterval(lock, dur); + } + } + +}; + +void rbd_bencher_completion(void *vc, void *pc) { + librbd::RBD::AioCompletion *c = (librbd::RBD::AioCompletion *)vc; + rbd_bencher *b = static_cast(pc); + //cout << "complete " << c << std::endl; + int ret = c->get_return_value(); + if (ret != 0) { + cout << "write error: " << cpp_strerror(ret) << std::endl; + exit(ret < 0 ? -ret : ret); + } + b->lock.Lock(); + b->in_flight--; + b->cond.Signal(); + b->lock.Unlock(); + c->release(); +} + +void write_image(librbd::Image &image) { + srand(time(NULL) % (unsigned long) -1); + + uint64_t max_io_bytes = MAX_IO_SIZE * 1024; + bufferptr bp(max_io_bytes); + memset(bp.c_str(), rand() & 0xff, bp.length()); + bufferlist bl; + bl.push_back(bp); + + uint64_t size = 0; + image.size(&size); + ceph_assert(size != 0); + + vector thread_offset; + uint64_t i; + uint64_t start_pos; + + // disturb all thread's offset, used by seq write + for (i = 0; i < NUM_THREADS; i++) { + start_pos = (rand() % (size / max_io_bytes)) * max_io_bytes; + thread_offset.push_back(start_pos); + } + + uint64_t total_ios = 0; + uint64_t total_bytes = 0; + rbd_bencher b(&image); + while (true) { + b.wait_for(NUM_THREADS - 1); + for (uint32_t i = 0; i < NUM_THREADS; ++i) { + // mostly small writes with a small chance of large writes + uint32_t io_modulo = MIN_IO_SIZE + 1; + if (rand() % 30 == 0) { + io_modulo += MAX_IO_SIZE; + } + + uint32_t io_size = (((rand() % io_modulo) + MIN_IO_SIZE) * 1024); + thread_offset[i] = (rand() % (size / io_size)) * io_size; + if (!b.start_write(NUM_THREADS, thread_offset[i], io_size, bl, + LIBRADOS_OP_FLAG_FADVISE_RANDOM)) { + break; + } + ++i; + + ++total_ios; + total_bytes += io_size; + if (total_ios % 100 == 0) { + std::cout << total_ios << " IOs, " << total_bytes << " bytes" + << std::endl; + } + } + } + b.wait_for(0); +} + +} // anonymous namespace + +int main(int argc, const char **argv) +{ + std::vector args; + argv_to_vec(argc, argv, args); + if (args.empty()) { + cerr << argv[0] << ": -h or --help for usage" << std::endl; + exit(1); + } + if (ceph_argparse_need_usage(args)) { + usage(); + exit(0); + } + + auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, + CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_MON_CONFIG); + + if (args.size() < 2) { + usage(); + return EXIT_FAILURE; + } + + std::string pool_name = args[0]; + std::string image_name = args[1]; + + common_init_finish(g_ceph_context); + + dout(5) << "connecting to cluster" << dendl; + librados::Rados rados; + librados::IoCtx io_ctx; + librbd::RBD rbd; + librbd::Image image; + int r = rados.init_with_context(g_ceph_context); + if (r < 0) { + derr << "could not initialize RADOS handle" << dendl; + return EXIT_FAILURE; + } + + r = rados.connect(); + if (r < 0) { + derr << "error connecting to local cluster" << dendl; + return EXIT_FAILURE; + } + + r = rados.ioctx_create(pool_name.c_str(), io_ctx); + if (r < 0) { + derr << "error finding local pool " << pool_name << ": " + << cpp_strerror(r) << dendl; + return EXIT_FAILURE; + } + + r = rbd.open(io_ctx, image, image_name.c_str()); + if (r < 0) { + derr << "error opening image " << image_name << ": " + << cpp_strerror(r) << dendl; + return EXIT_FAILURE; + } + + write_image(image); + return EXIT_SUCCESS; +} diff --git a/src/test/rbd_mirror/test_ClusterWatcher.cc b/src/test/rbd_mirror/test_ClusterWatcher.cc new file mode 100644 index 00000000..05ca2c10 --- /dev/null +++ b/src/test/rbd_mirror/test_ClusterWatcher.cc @@ -0,0 +1,254 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include "include/rados/librados.hpp" +#include "common/Cond.h" +#include "common/errno.h" +#include "common/Mutex.h" +#include "librbd/internal.h" +#include "librbd/api/Mirror.h" +#include "tools/rbd_mirror/ClusterWatcher.h" +#include "tools/rbd_mirror/ServiceDaemon.h" +#include "tools/rbd_mirror/Types.h" +#include "test/rbd_mirror/test_fixture.h" +#include "test/librados/test_cxx.h" +#include "test/librbd/test_support.h" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include + +using rbd::mirror::ClusterWatcher; +using rbd::mirror::PeerSpec; +using rbd::mirror::RadosRef; +using std::map; +using std::set; +using std::string; + +void register_test_cluster_watcher() { +} + +class TestClusterWatcher : public ::rbd::mirror::TestFixture { +public: + + TestClusterWatcher() : m_lock("TestClusterWatcherLock") + { + m_cluster = std::make_shared(); + EXPECT_EQ("", connect_cluster_pp(*m_cluster)); + } + + ~TestClusterWatcher() override { + m_cluster->wait_for_latest_osdmap(); + for (auto& pool : m_pools) { + EXPECT_EQ(0, m_cluster->pool_delete(pool.c_str())); + } + } + + void SetUp() override { + TestFixture::SetUp(); + m_service_daemon.reset(new rbd::mirror::ServiceDaemon<>(g_ceph_context, + m_cluster, + m_threads)); + m_cluster_watcher.reset(new ClusterWatcher(m_cluster, m_lock, + m_service_daemon.get())); + } + + void TearDown() override { + m_service_daemon.reset(); + m_cluster_watcher.reset(); + TestFixture::TearDown(); + } + + void create_pool(bool enable_mirroring, const PeerSpec &peer, + string *uuid = nullptr, string *name=nullptr) { + string pool_name = get_temp_pool_name("test-rbd-mirror-"); + ASSERT_EQ(0, m_cluster->pool_create(pool_name.c_str())); + + int64_t pool_id = m_cluster->pool_lookup(pool_name.c_str()); + ASSERT_GE(pool_id, 0); + + librados::IoCtx ioctx; + ASSERT_EQ(0, m_cluster->ioctx_create2(pool_id, ioctx)); + ioctx.application_enable("rbd", true); + + m_pools.insert(pool_name); + if (enable_mirroring) { + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(ioctx, + RBD_MIRROR_MODE_POOL)); + + std::string gen_uuid; + ASSERT_EQ(0, librbd::api::Mirror<>::peer_add(ioctx, + uuid != nullptr ? uuid : + &gen_uuid, + peer.cluster_name, + peer.client_name)); + m_pool_peers[pool_id].insert(peer); + } + if (name != nullptr) { + *name = pool_name; + } + } + + void delete_pool(const string &name, const PeerSpec &peer) { + int64_t pool_id = m_cluster->pool_lookup(name.c_str()); + ASSERT_GE(pool_id, 0); + if (m_pool_peers.find(pool_id) != m_pool_peers.end()) { + m_pool_peers[pool_id].erase(peer); + if (m_pool_peers[pool_id].empty()) { + m_pool_peers.erase(pool_id); + } + } + m_pools.erase(name); + ASSERT_EQ(0, m_cluster->pool_delete(name.c_str())); + } + + void set_peer_config_key(const std::string& pool_name, + const PeerSpec &peer) { + int64_t pool_id = m_cluster->pool_lookup(pool_name.c_str()); + ASSERT_GE(pool_id, 0); + + std::string json = + "{" + "\\\"mon_host\\\": \\\"" + peer.mon_host + "\\\", " + "\\\"key\\\": \\\"" + peer.key + "\\\"" + "}"; + + bufferlist in_bl; + ASSERT_EQ(0, m_cluster->mon_command( + "{" + "\"prefix\": \"config-key set\"," + "\"key\": \"" RBD_MIRROR_PEER_CONFIG_KEY_PREFIX + stringify(pool_id) + + "/" + peer.uuid + "\"," + "\"val\": \"" + json + "\"" + + "}", in_bl, nullptr, nullptr)); + } + + void create_cache_pool(const string &base_pool, string *cache_pool_name) { + bufferlist inbl; + *cache_pool_name = get_temp_pool_name("test-rbd-mirror-"); + ASSERT_EQ(0, m_cluster->pool_create(cache_pool_name->c_str())); + + ASSERT_EQ(0, m_cluster->mon_command( + "{\"prefix\": \"osd tier add\", \"pool\": \"" + base_pool + + "\", \"tierpool\": \"" + *cache_pool_name + + "\", \"force_nonempty\": \"--force-nonempty\" }", + inbl, NULL, NULL)); + ASSERT_EQ(0, m_cluster->mon_command( + "{\"prefix\": \"osd tier set-overlay\", \"pool\": \"" + base_pool + + "\", \"overlaypool\": \"" + *cache_pool_name + "\"}", + inbl, NULL, NULL)); + ASSERT_EQ(0, m_cluster->mon_command( + "{\"prefix\": \"osd tier cache-mode\", \"pool\": \"" + *cache_pool_name + + "\", \"mode\": \"writeback\"}", + inbl, NULL, NULL)); + m_cluster->wait_for_latest_osdmap(); + } + + void remove_cache_pool(const string &base_pool, const string &cache_pool) { + bufferlist inbl; + // tear down tiers + ASSERT_EQ(0, m_cluster->mon_command( + "{\"prefix\": \"osd tier remove-overlay\", \"pool\": \"" + base_pool + + "\"}", + inbl, NULL, NULL)); + ASSERT_EQ(0, m_cluster->mon_command( + "{\"prefix\": \"osd tier remove\", \"pool\": \"" + base_pool + + "\", \"tierpool\": \"" + cache_pool + "\"}", + inbl, NULL, NULL)); + m_cluster->wait_for_latest_osdmap(); + m_cluster->pool_delete(cache_pool.c_str()); + } + + void check_peers() { + m_cluster_watcher->refresh_pools(); + Mutex::Locker l(m_lock); + ASSERT_EQ(m_pool_peers, m_cluster_watcher->get_pool_peers()); + } + + RadosRef m_cluster; + Mutex m_lock; + unique_ptr> m_service_daemon; + unique_ptr m_cluster_watcher; + + set m_pools; + ClusterWatcher::PoolPeers m_pool_peers; +}; + +TEST_F(TestClusterWatcher, NoPools) { + check_peers(); +} + +TEST_F(TestClusterWatcher, NoMirroredPools) { + check_peers(); + create_pool(false, PeerSpec()); + check_peers(); + create_pool(false, PeerSpec()); + check_peers(); + create_pool(false, PeerSpec()); + check_peers(); +} + +TEST_F(TestClusterWatcher, ReplicatedPools) { + PeerSpec site1("", "site1", "mirror1"); + PeerSpec site2("", "site2", "mirror2"); + string first_pool, last_pool; + check_peers(); + create_pool(true, site1, &site1.uuid, &first_pool); + check_peers(); + create_pool(false, PeerSpec()); + check_peers(); + create_pool(false, PeerSpec()); + check_peers(); + create_pool(false, PeerSpec()); + check_peers(); + create_pool(true, site2, &site2.uuid); + check_peers(); + create_pool(true, site2, &site2.uuid); + check_peers(); + create_pool(true, site2, &site2.uuid, &last_pool); + check_peers(); + delete_pool(first_pool, site1); + check_peers(); + delete_pool(last_pool, site2); + check_peers(); +} + +TEST_F(TestClusterWatcher, CachePools) { + PeerSpec site1("", "site1", "mirror1"); + string base1, base2, cache1, cache2; + create_pool(true, site1, &site1.uuid, &base1); + check_peers(); + + create_cache_pool(base1, &cache1); + BOOST_SCOPE_EXIT( base1, cache1, this_ ) { + this_->remove_cache_pool(base1, cache1); + } BOOST_SCOPE_EXIT_END; + check_peers(); + + create_pool(false, PeerSpec(), nullptr, &base2); + create_cache_pool(base2, &cache2); + BOOST_SCOPE_EXIT( base2, cache2, this_ ) { + this_->remove_cache_pool(base2, cache2); + } BOOST_SCOPE_EXIT_END; + check_peers(); +} + +TEST_F(TestClusterWatcher, ConfigKey) { + REQUIRE(!is_librados_test_stub(*m_cluster)); + + std::string pool_name; + check_peers(); + + PeerSpec site1("", "site1", "mirror1"); + create_pool(true, site1, &site1.uuid, &pool_name); + check_peers(); + + PeerSpec site2("", "site2", "mirror2"); + site2.mon_host = "abc"; + site2.key = "xyz"; + create_pool(false, site2, &site2.uuid); + set_peer_config_key(pool_name, site2); + + check_peers(); +} diff --git a/src/test/rbd_mirror/test_ImageDeleter.cc b/src/test/rbd_mirror/test_ImageDeleter.cc new file mode 100644 index 00000000..93f311e3 --- /dev/null +++ b/src/test/rbd_mirror/test_ImageDeleter.cc @@ -0,0 +1,301 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include "include/rados/librados.hpp" +#include "include/rbd/librbd.hpp" +#include "include/stringify.h" +#include "cls/rbd/cls_rbd_types.h" +#include "cls/rbd/cls_rbd_client.h" +#include "tools/rbd_mirror/ImageDeleter.h" +#include "tools/rbd_mirror/ServiceDaemon.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/Types.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/Journal.h" +#include "librbd/internal.h" +#include "librbd/Utils.h" +#include "librbd/api/Image.h" +#include "librbd/api/Mirror.h" +#include "librbd/journal/DisabledPolicy.h" +#include "test/rbd_mirror/test_fixture.h" + +#include "test/librados/test.h" +#include "gtest/gtest.h" + +#define GLOBAL_IMAGE_ID "global_image_id" +#define GLOBAL_CLONE_IMAGE_ID "global_image_id_clone" + +#define dout_subsys ceph_subsys_rbd_mirror + +using rbd::mirror::RadosRef; +using rbd::mirror::TestFixture; +using namespace librbd; +using cls::rbd::MirrorImageState; + + +void register_test_rbd_mirror_image_deleter() { +} + +class TestImageDeleter : public TestFixture { +public: + const std::string m_local_mirror_uuid = "local mirror uuid"; + const std::string m_remote_mirror_uuid = "remote mirror uuid"; + + void SetUp() override { + TestFixture::SetUp(); + + m_service_daemon.reset(new rbd::mirror::ServiceDaemon<>(g_ceph_context, + _rados, m_threads)); + + librbd::api::Mirror<>::mode_set(m_local_io_ctx, RBD_MIRROR_MODE_IMAGE); + + m_deleter = new rbd::mirror::ImageDeleter<>(m_local_io_ctx, m_threads, + m_service_daemon.get()); + + m_local_image_id = librbd::util::generate_image_id(m_local_io_ctx); + librbd::ImageOptions image_opts; + image_opts.set(RBD_IMAGE_OPTION_FEATURES, RBD_FEATURES_ALL); + EXPECT_EQ(0, librbd::create(m_local_io_ctx, m_image_name, m_local_image_id, + 1 << 20, image_opts, GLOBAL_IMAGE_ID, + m_remote_mirror_uuid, true)); + + cls::rbd::MirrorImage mirror_image( + GLOBAL_IMAGE_ID, MirrorImageState::MIRROR_IMAGE_STATE_ENABLED); + EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, m_local_image_id, + mirror_image)); + } + + void TearDown() override { + remove_image(); + + C_SaferCond ctx; + m_deleter->shut_down(&ctx); + ctx.wait(); + + delete m_deleter; + m_service_daemon.reset(); + + TestFixture::TearDown(); + } + + void init_image_deleter() { + C_SaferCond ctx; + m_deleter->init(&ctx); + ASSERT_EQ(0, ctx.wait()); + } + + void remove_image() { + cls::rbd::MirrorImage mirror_image; + int r = cls_client::mirror_image_get(&m_local_io_ctx, m_local_image_id, + &mirror_image); + EXPECT_EQ(1, r == 0 || r == -ENOENT); + if (r != -ENOENT) { + mirror_image.state = MirrorImageState::MIRROR_IMAGE_STATE_ENABLED; + EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, + m_local_image_id, + mirror_image)); + } + promote_image(); + + NoOpProgressContext ctx; + r = librbd::api::Image<>::remove(m_local_io_ctx, m_image_name, ctx); + EXPECT_EQ(1, r == 0 || r == -ENOENT); + } + + void promote_image(ImageCtx *ictx=nullptr) { + bool close = false; + int r = 0; + if (!ictx) { + ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx, + false); + r = ictx->state->open(0); + close = (r == 0); + } + + EXPECT_EQ(1, r == 0 || r == -ENOENT); + + if (r == 0) { + int r2 = librbd::api::Mirror<>::image_promote(ictx, true); + EXPECT_EQ(1, r2 == 0 || r2 == -EINVAL); + } + + if (close) { + EXPECT_EQ(0, ictx->state->close()); + } + } + + void demote_image(ImageCtx *ictx=nullptr) { + bool close = false; + if (!ictx) { + ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx, + false); + EXPECT_EQ(0, ictx->state->open(0)); + close = true; + } + + EXPECT_EQ(0, librbd::api::Mirror<>::image_demote(ictx)); + + if (close) { + EXPECT_EQ(0, ictx->state->close()); + } + } + + void create_snapshot(std::string snap_name="snap1", bool protect=false) { + ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx, + false); + EXPECT_EQ(0, ictx->state->open(0)); + { + RWLock::WLocker snap_locker(ictx->snap_lock); + ictx->set_journal_policy(new librbd::journal::DisabledPolicy()); + } + + EXPECT_EQ(0, ictx->operations->snap_create( + cls::rbd::UserSnapshotNamespace(), snap_name.c_str())); + + if (protect) { + EXPECT_EQ(0, ictx->operations->snap_protect( + cls::rbd::UserSnapshotNamespace(), snap_name.c_str())); + } + + EXPECT_EQ(0, ictx->state->close()); + } + + std::string create_clone() { + ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx, + false); + EXPECT_EQ(0, ictx->state->open(0)); + { + RWLock::WLocker snap_locker(ictx->snap_lock); + ictx->set_journal_policy(new librbd::journal::DisabledPolicy()); + } + + EXPECT_EQ(0, ictx->operations->snap_create( + cls::rbd::UserSnapshotNamespace(), "snap1")); + EXPECT_EQ(0, ictx->operations->snap_protect( + cls::rbd::UserSnapshotNamespace(), "snap1")); + EXPECT_EQ(0, librbd::api::Image<>::snap_set( + ictx, cls::rbd::UserSnapshotNamespace(), "snap1")); + + std::string clone_id = librbd::util::generate_image_id(m_local_io_ctx); + librbd::ImageOptions clone_opts; + clone_opts.set(RBD_IMAGE_OPTION_FEATURES, ictx->features); + EXPECT_EQ(0, librbd::clone(m_local_io_ctx, m_local_image_id.c_str(), + nullptr, "snap1", m_local_io_ctx, + clone_id.c_str(), "clone1", clone_opts, + GLOBAL_CLONE_IMAGE_ID, m_remote_mirror_uuid)); + + cls::rbd::MirrorImage mirror_image( + GLOBAL_CLONE_IMAGE_ID, MirrorImageState::MIRROR_IMAGE_STATE_ENABLED); + EXPECT_EQ(0, cls_client::mirror_image_set(&m_local_io_ctx, clone_id, + mirror_image)); + EXPECT_EQ(0, ictx->state->close()); + return clone_id; + } + + void check_image_deleted() { + ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx, + false); + EXPECT_EQ(-ENOENT, ictx->state->open(0)); + + cls::rbd::MirrorImage mirror_image; + EXPECT_EQ(-ENOENT, cls_client::mirror_image_get(&m_local_io_ctx, + m_local_image_id, + &mirror_image)); + } + + int trash_move(const std::string& global_image_id) { + C_SaferCond ctx; + rbd::mirror::ImageDeleter<>::trash_move(m_local_io_ctx, global_image_id, + true, m_threads->work_queue, &ctx); + return ctx.wait(); + } + + librbd::RBD rbd; + std::string m_local_image_id; + std::unique_ptr> m_service_daemon; + rbd::mirror::ImageDeleter<> *m_deleter; +}; + +TEST_F(TestImageDeleter, ExistingTrashMove) { + ASSERT_EQ(0, trash_move(GLOBAL_IMAGE_ID)); + + C_SaferCond ctx; + m_deleter->wait_for_deletion(m_local_image_id, false, &ctx); + init_image_deleter(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestImageDeleter, LiveTrashMove) { + init_image_deleter(); + + C_SaferCond ctx; + m_deleter->wait_for_deletion(m_local_image_id, false, &ctx); + + ASSERT_EQ(0, trash_move(GLOBAL_IMAGE_ID)); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestImageDeleter, Delete_Image_With_Snapshots) { + init_image_deleter(); + create_snapshot("snap1"); + create_snapshot("snap2"); + + C_SaferCond ctx; + m_deleter->wait_for_deletion(m_local_image_id, false, &ctx); + ASSERT_EQ(0, trash_move(GLOBAL_IMAGE_ID)); + EXPECT_EQ(0, ctx.wait()); + + ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size()); + ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size()); +} + +TEST_F(TestImageDeleter, Delete_Image_With_ProtectedSnapshots) { + init_image_deleter(); + create_snapshot("snap1", true); + create_snapshot("snap2", true); + + C_SaferCond ctx; + m_deleter->wait_for_deletion(m_local_image_id, false, &ctx); + ASSERT_EQ(0, trash_move(GLOBAL_IMAGE_ID)); + EXPECT_EQ(0, ctx.wait()); + + ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size()); + ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size()); +} + +TEST_F(TestImageDeleter, Delete_Image_With_Clone) { + init_image_deleter(); + std::string clone_id = create_clone(); + + C_SaferCond ctx1; + m_deleter->set_busy_timer_interval(0.1); + m_deleter->wait_for_deletion(m_local_image_id, false, &ctx1); + ASSERT_EQ(0, trash_move(GLOBAL_IMAGE_ID)); + EXPECT_EQ(-EBUSY, ctx1.wait()); + + C_SaferCond ctx2; + m_deleter->wait_for_deletion(clone_id, false, &ctx2); + ASSERT_EQ(0, trash_move(GLOBAL_CLONE_IMAGE_ID)); + EXPECT_EQ(0, ctx2.wait()); + + C_SaferCond ctx3; + m_deleter->wait_for_deletion(m_local_image_id, true, &ctx3); + EXPECT_EQ(0, ctx3.wait()); + + ASSERT_EQ(0u, m_deleter->get_delete_queue_items().size()); + ASSERT_EQ(0u, m_deleter->get_failed_queue_items().size()); +} + diff --git a/src/test/rbd_mirror/test_ImageReplayer.cc b/src/test/rbd_mirror/test_ImageReplayer.cc new file mode 100644 index 00000000..67adc18a --- /dev/null +++ b/src/test/rbd_mirror/test_ImageReplayer.cc @@ -0,0 +1,1162 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph distributed storage system + * + * Copyright (C) 2016 Mirantis Inc + * + * Author: Mykola Golub + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + */ + +#include "include/rados/librados.hpp" +#include "include/rbd/librbd.hpp" +#include "include/stringify.h" +#include "test/rbd_mirror/test_fixture.h" +#include "cls/journal/cls_journal_types.h" +#include "cls/journal/cls_journal_client.h" +#include "cls/rbd/cls_rbd_types.h" +#include "cls/rbd/cls_rbd_client.h" +#include "journal/Journaler.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Journal.h" +#include "librbd/Operations.h" +#include "librbd/Utils.h" +#include "librbd/internal.h" +#include "librbd/api/Mirror.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" +#include "tools/rbd_mirror/ImageReplayer.h" +#include "tools/rbd_mirror/InstanceWatcher.h" +#include "tools/rbd_mirror/ServiceDaemon.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/Types.h" + +#include "test/librados/test_cxx.h" +#include "gtest/gtest.h" + +using rbd::mirror::RadosRef; + +void register_test_rbd_mirror() { +} + +#define TEST_IO_SIZE 512 +#define TEST_IO_COUNT 11 + +class TestImageReplayer : public ::rbd::mirror::TestFixture { +public: + struct C_WatchCtx : public librados::WatchCtx2 { + TestImageReplayer *test; + std::string oid; + Mutex lock; + Cond cond; + bool notified; + + C_WatchCtx(TestImageReplayer *test, const std::string &oid) + : test(test), oid(oid), lock("C_WatchCtx::lock"), notified(false) { + } + + void handle_notify(uint64_t notify_id, uint64_t cookie, + uint64_t notifier_id, bufferlist& bl_) override { + bufferlist bl; + test->m_remote_ioctx.notify_ack(oid, notify_id, cookie, bl); + + Mutex::Locker locker(lock); + notified = true; + cond.Signal(); + } + + void handle_error(uint64_t cookie, int err) override { + ASSERT_EQ(0, err); + } + }; + + TestImageReplayer() + : m_local_cluster(new librados::Rados()), m_watch_handle(0) + { + EXPECT_EQ("", connect_cluster_pp(*m_local_cluster.get())); + EXPECT_EQ(0, m_local_cluster->conf_set("rbd_cache", "false")); + EXPECT_EQ(0, m_local_cluster->conf_set("rbd_mirror_journal_poll_age", "1")); + EXPECT_EQ(0, m_local_cluster->conf_set("rbd_mirror_journal_commit_age", + "0.1")); + m_local_pool_name = get_temp_pool_name(); + EXPECT_EQ(0, m_local_cluster->pool_create(m_local_pool_name.c_str())); + EXPECT_EQ(0, m_local_cluster->ioctx_create(m_local_pool_name.c_str(), + m_local_ioctx)); + m_local_ioctx.application_enable("rbd", true); + + EXPECT_EQ("", connect_cluster_pp(m_remote_cluster)); + EXPECT_EQ(0, m_remote_cluster.conf_set("rbd_cache", "false")); + + m_remote_pool_name = get_temp_pool_name(); + EXPECT_EQ(0, m_remote_cluster.pool_create(m_remote_pool_name.c_str())); + m_remote_pool_id = m_remote_cluster.pool_lookup(m_remote_pool_name.c_str()); + EXPECT_GE(m_remote_pool_id, 0); + + EXPECT_EQ(0, m_remote_cluster.ioctx_create(m_remote_pool_name.c_str(), + m_remote_ioctx)); + m_remote_ioctx.application_enable("rbd", true); + + EXPECT_EQ(0, librbd::api::Mirror<>::mode_set(m_remote_ioctx, + RBD_MIRROR_MODE_POOL)); + + m_image_name = get_temp_image_name(); + uint64_t features = librbd::util::get_rbd_default_features(g_ceph_context); + features |= RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING; + int order = 0; + EXPECT_EQ(0, librbd::create(m_remote_ioctx, m_image_name.c_str(), 1 << 22, + false, features, &order, 0, 0)); + m_remote_image_id = get_image_id(m_remote_ioctx, m_image_name); + m_global_image_id = get_global_image_id(m_remote_ioctx, m_remote_image_id); + + m_threads.reset(new rbd::mirror::Threads<>(reinterpret_cast( + m_local_ioctx.cct()))); + + m_service_daemon.reset(new rbd::mirror::ServiceDaemon<>(g_ceph_context, + m_local_cluster, + m_threads.get())); + + m_instance_watcher = rbd::mirror::InstanceWatcher<>::create( + m_local_ioctx, m_threads->work_queue, nullptr); + m_instance_watcher->handle_acquire_leader(); + } + + ~TestImageReplayer() override + { + unwatch(); + + m_instance_watcher->handle_release_leader(); + + delete m_replayer; + delete m_instance_watcher; + + EXPECT_EQ(0, m_remote_cluster.pool_delete(m_remote_pool_name.c_str())); + EXPECT_EQ(0, m_local_cluster->pool_delete(m_local_pool_name.c_str())); + } + + template > + void create_replayer() { + m_replayer = new ImageReplayerT( + m_threads.get(), m_instance_watcher, + rbd::mirror::RadosRef(new librados::Rados(m_local_ioctx)), + m_local_mirror_uuid, m_local_ioctx.get_id(), m_global_image_id); + m_replayer->add_peer("peer uuid", m_remote_ioctx); + } + + void start() + { + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(0, cond.wait()); + + ASSERT_EQ(0U, m_watch_handle); + std::string oid = ::journal::Journaler::header_oid(m_remote_image_id); + m_watch_ctx = new C_WatchCtx(this, oid); + ASSERT_EQ(0, m_remote_ioctx.watch2(oid, &m_watch_handle, m_watch_ctx)); + } + + void unwatch() { + if (m_watch_handle != 0) { + m_remote_ioctx.unwatch2(m_watch_handle); + delete m_watch_ctx; + m_watch_ctx = nullptr; + m_watch_handle = 0; + } + } + + void stop() + { + unwatch(); + + C_SaferCond cond; + m_replayer->stop(&cond); + ASSERT_EQ(0, cond.wait()); + } + + void bootstrap() + { + create_replayer<>(); + + start(); + wait_for_replay_complete(); + stop(); + } + + std::string get_temp_image_name() + { + return "image" + stringify(++_image_number); + } + + std::string get_image_id(librados::IoCtx &ioctx, const string &image_name) + { + std::string obj = librbd::util::id_obj_name(image_name); + std::string id; + EXPECT_EQ(0, librbd::cls_client::get_id(&ioctx, obj, &id)); + return id; + } + + std::string get_global_image_id(librados::IoCtx& io_ctx, + const std::string& image_id) { + cls::rbd::MirrorImage mirror_image; + EXPECT_EQ(0, librbd::cls_client::mirror_image_get(&io_ctx, image_id, + &mirror_image)); + return mirror_image.global_image_id; + } + + void open_image(librados::IoCtx &ioctx, const std::string &image_name, + bool readonly, librbd::ImageCtx **ictxp) + { + librbd::ImageCtx *ictx = new librbd::ImageCtx(image_name.c_str(), + "", "", ioctx, readonly); + EXPECT_EQ(0, ictx->state->open(0)); + *ictxp = ictx; + } + + void open_local_image(librbd::ImageCtx **ictxp) + { + open_image(m_local_ioctx, m_image_name, true, ictxp); + } + + void open_remote_image(librbd::ImageCtx **ictxp) + { + open_image(m_remote_ioctx, m_image_name, false, ictxp); + } + + void close_image(librbd::ImageCtx *ictx) + { + ictx->state->close(); + } + + void get_commit_positions(cls::journal::ObjectPosition *master_position, + cls::journal::ObjectPosition *mirror_position) + { + std::string master_client_id = ""; + std::string mirror_client_id = m_local_mirror_uuid; + + C_SaferCond cond; + uint64_t minimum_set; + uint64_t active_set; + std::set registered_clients; + std::string oid = ::journal::Journaler::header_oid(m_remote_image_id); + cls::journal::client::get_mutable_metadata(m_remote_ioctx, oid, + &minimum_set, &active_set, + ®istered_clients, &cond); + ASSERT_EQ(0, cond.wait()); + + *master_position = cls::journal::ObjectPosition(); + *mirror_position = cls::journal::ObjectPosition(); + + std::set::const_iterator c; + for (c = registered_clients.begin(); c != registered_clients.end(); ++c) { + std::cout << __func__ << ": client: " << *c << std::endl; + if (c->state != cls::journal::CLIENT_STATE_CONNECTED) { + continue; + } + cls::journal::ObjectPositions object_positions = + c->commit_position.object_positions; + cls::journal::ObjectPositions::const_iterator p = + object_positions.begin(); + if (p != object_positions.end()) { + if (c->id == master_client_id) { + ASSERT_EQ(cls::journal::ObjectPosition(), *master_position); + *master_position = *p; + } else if (c->id == mirror_client_id) { + ASSERT_EQ(cls::journal::ObjectPosition(), *mirror_position); + *mirror_position = *p; + } + } + } + } + + bool wait_for_watcher_notify(int seconds) + { + if (m_watch_handle == 0) { + return false; + } + + Mutex::Locker locker(m_watch_ctx->lock); + while (!m_watch_ctx->notified) { + if (m_watch_ctx->cond.WaitInterval(m_watch_ctx->lock, + utime_t(seconds, 0)) != 0) { + return false; + } + } + m_watch_ctx->notified = false; + return true; + } + + void wait_for_replay_complete() + { + cls::journal::ObjectPosition master_position; + cls::journal::ObjectPosition mirror_position; + + for (int i = 0; i < 100; i++) { + get_commit_positions(&master_position, &mirror_position); + if (master_position == mirror_position) { + break; + } + wait_for_watcher_notify(1); + } + + ASSERT_EQ(master_position, mirror_position); + } + + void wait_for_stopped() { + for (int i = 0; i < 100; i++) { + if (m_replayer->is_stopped()) { + break; + } + wait_for_watcher_notify(1); + } + ASSERT_TRUE(m_replayer->is_stopped()); + } + + void write_test_data(librbd::ImageCtx *ictx, const char *test_data, off_t off, + size_t len) + { + size_t written; + bufferlist bl; + bl.append(std::string(test_data, len)); + written = ictx->io_work_queue->write(off, len, std::move(bl), 0); + printf("wrote: %d\n", (int)written); + ASSERT_EQ(len, written); + } + + void read_test_data(librbd::ImageCtx *ictx, const char *expected, off_t off, + size_t len) + { + ssize_t read; + char *result = (char *)malloc(len + 1); + + ASSERT_NE(static_cast(NULL), result); + read = ictx->io_work_queue->read( + off, len, librbd::io::ReadResult{result, len}, 0); + printf("read: %d\n", (int)read); + ASSERT_EQ(len, static_cast(read)); + result[len] = '\0'; + if (memcmp(result, expected, len)) { + printf("read: %s\nexpected: %s\n", result, expected); + ASSERT_EQ(0, memcmp(result, expected, len)); + } + free(result); + } + + void generate_test_data() { + for (int i = 0; i < TEST_IO_SIZE; ++i) { + m_test_data[i] = (char) (rand() % (126 - 33) + 33); + } + m_test_data[TEST_IO_SIZE] = '\0'; + } + + void flush(librbd::ImageCtx *ictx) + { + C_SaferCond aio_flush_ctx; + auto c = librbd::io::AioCompletion::create(&aio_flush_ctx); + c->get(); + ictx->io_work_queue->aio_flush(c); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + C_SaferCond journal_flush_ctx; + ictx->journal->flush_commit_position(&journal_flush_ctx); + ASSERT_EQ(0, journal_flush_ctx.wait()); + + printf("flushed\n"); + } + + static int _image_number; + + std::shared_ptr m_local_cluster; + std::unique_ptr> m_threads; + std::unique_ptr> m_service_daemon; + librados::Rados m_remote_cluster; + rbd::mirror::InstanceWatcher<> *m_instance_watcher; + std::string m_local_mirror_uuid = "local mirror uuid"; + std::string m_remote_mirror_uuid = "remote mirror uuid"; + std::string m_local_pool_name, m_remote_pool_name; + librados::IoCtx m_local_ioctx, m_remote_ioctx; + std::string m_image_name; + int64_t m_remote_pool_id; + std::string m_remote_image_id; + std::string m_global_image_id; + rbd::mirror::ImageReplayer<> *m_replayer; + C_WatchCtx *m_watch_ctx; + uint64_t m_watch_handle; + char m_test_data[TEST_IO_SIZE + 1]; + std::string m_journal_commit_age; +}; + +int TestImageReplayer::_image_number; + +TEST_F(TestImageReplayer, Bootstrap) +{ + bootstrap(); +} + +TEST_F(TestImageReplayer, BootstrapErrorLocalImageExists) +{ + int order = 0; + EXPECT_EQ(0, librbd::create(m_local_ioctx, m_image_name.c_str(), 1 << 22, + false, 0, &order, 0, 0)); + + create_replayer<>(); + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(-EEXIST, cond.wait()); +} + +TEST_F(TestImageReplayer, BootstrapErrorNoJournal) +{ + ASSERT_EQ(0, librbd::Journal<>::remove(m_remote_ioctx, m_remote_image_id)); + + create_replayer<>(); + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(-ENOENT, cond.wait()); +} + +TEST_F(TestImageReplayer, BootstrapErrorMirrorDisabled) +{ + // disable remote image mirroring + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(m_remote_ioctx, + RBD_MIRROR_MODE_IMAGE)); + librbd::ImageCtx *ictx; + open_remote_image(&ictx); + ASSERT_EQ(0, librbd::api::Mirror<>::image_disable(ictx, true)); + close_image(ictx); + + create_replayer<>(); + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(-ENOENT, cond.wait()); +} + +TEST_F(TestImageReplayer, BootstrapMirrorDisabling) +{ + // set remote image mirroring state to DISABLING + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(m_remote_ioctx, + RBD_MIRROR_MODE_IMAGE)); + librbd::ImageCtx *ictx; + open_remote_image(&ictx); + ASSERT_EQ(0, librbd::api::Mirror<>::image_enable(ictx, false)); + cls::rbd::MirrorImage mirror_image; + ASSERT_EQ(0, librbd::cls_client::mirror_image_get(&m_remote_ioctx, ictx->id, + &mirror_image)); + mirror_image.state = cls::rbd::MirrorImageState::MIRROR_IMAGE_STATE_DISABLING; + ASSERT_EQ(0, librbd::cls_client::mirror_image_set(&m_remote_ioctx, ictx->id, + mirror_image)); + close_image(ictx); + + create_replayer<>(); + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(-EREMOTEIO, cond.wait()); + ASSERT_TRUE(m_replayer->is_stopped()); +} + +TEST_F(TestImageReplayer, BootstrapDemoted) +{ + // demote remote image + librbd::ImageCtx *ictx; + open_remote_image(&ictx); + ASSERT_EQ(0, librbd::api::Mirror<>::image_demote(ictx)); + close_image(ictx); + + create_replayer<>(); + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(-EREMOTEIO, cond.wait()); + ASSERT_TRUE(m_replayer->is_stopped()); +} + +TEST_F(TestImageReplayer, StartInterrupted) +{ + create_replayer<>(); + C_SaferCond start_cond, stop_cond; + m_replayer->start(&start_cond); + m_replayer->stop(&stop_cond); + int r = start_cond.wait(); + printf("start returned %d\n", r); + // TODO: improve the test to avoid this race + ASSERT_TRUE(r == -ECANCELED || r == 0); + ASSERT_EQ(0, stop_cond.wait()); +} + +TEST_F(TestImageReplayer, JournalReset) +{ + bootstrap(); + delete m_replayer; + + ASSERT_EQ(0, librbd::Journal<>::reset(m_remote_ioctx, m_remote_image_id)); + + // try to recover + bootstrap(); +} + +TEST_F(TestImageReplayer, ErrorNoJournal) +{ + bootstrap(); + + // disable remote journal journaling + // (reset before disabling, so it does not fail with EBUSY) + ASSERT_EQ(0, librbd::Journal<>::reset(m_remote_ioctx, m_remote_image_id)); + librbd::ImageCtx *ictx; + open_remote_image(&ictx); + uint64_t features; + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0, ictx->operations->update_features(RBD_FEATURE_JOURNALING, + false)); + close_image(ictx); + + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(0, cond.wait()); +} + +TEST_F(TestImageReplayer, StartStop) +{ + bootstrap(); + + start(); + wait_for_replay_complete(); + stop(); +} + +TEST_F(TestImageReplayer, WriteAndStartReplay) +{ + bootstrap(); + + // Write to remote image and start replay + + librbd::ImageCtx *ictx; + + generate_test_data(); + open_remote_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + start(); + wait_for_replay_complete(); + stop(); + + open_local_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + read_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); +} + +TEST_F(TestImageReplayer, StartReplayAndWrite) +{ + bootstrap(); + + // Start replay and write to remote image + + librbd::ImageCtx *ictx; + + start(); + + generate_test_data(); + open_remote_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + + wait_for_replay_complete(); + + for (int i = TEST_IO_COUNT; i < 2 * TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + wait_for_replay_complete(); + + open_local_image(&ictx); + for (int i = 0; i < 2 * TEST_IO_COUNT; ++i) { + read_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + + stop(); +} + +TEST_F(TestImageReplayer, NextTag) +{ + bootstrap(); + + // write, reopen, and write again to test switch to the next tag + + librbd::ImageCtx *ictx; + + start(); + + generate_test_data(); + + const int N = 10; + + for (int j = 0; j < N; j++) { + open_remote_image(&ictx); + for (int i = j * TEST_IO_COUNT; i < (j + 1) * TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + } + + wait_for_replay_complete(); + + open_local_image(&ictx); + for (int i = 0; i < N * TEST_IO_COUNT; ++i) { + read_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + + stop(); +} + +TEST_F(TestImageReplayer, Resync) +{ + bootstrap(); + + librbd::ImageCtx *ictx; + + start(); + + generate_test_data(); + + open_remote_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + + wait_for_replay_complete(); + + for (int i = TEST_IO_COUNT; i < 2 * TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + C_SaferCond ctx; + m_replayer->resync_image(&ctx); + ASSERT_EQ(0, ctx.wait()); + + wait_for_stopped(); + + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(0, cond.wait()); + + ASSERT_TRUE(m_replayer->is_replaying()); + wait_for_replay_complete(); + + open_local_image(&ictx); + for (int i = 0; i < 2 * TEST_IO_COUNT; ++i) { + read_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + + stop(); +} + +TEST_F(TestImageReplayer, Resync_While_Stop) +{ + + bootstrap(); + + start(); + + generate_test_data(); + + librbd::ImageCtx *ictx; + open_remote_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + + wait_for_replay_complete(); + + for (int i = TEST_IO_COUNT; i < 2 * TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + wait_for_replay_complete(); + + C_SaferCond cond; + m_replayer->stop(&cond); + ASSERT_EQ(0, cond.wait()); + + open_local_image(&ictx); + librbd::Journal<>::request_resync(ictx); + close_image(ictx); + + C_SaferCond cond2; + m_replayer->start(&cond2); + ASSERT_EQ(0, cond2.wait()); + + ASSERT_TRUE(m_replayer->is_stopped()); + + C_SaferCond cond3; + m_replayer->start(&cond3); + ASSERT_EQ(0, cond3.wait()); + + ASSERT_TRUE(m_replayer->is_replaying()); + + wait_for_replay_complete(); + + open_local_image(&ictx); + for (int i = 0; i < 2 * TEST_IO_COUNT; ++i) { + read_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + + stop(); +} + +TEST_F(TestImageReplayer, Resync_StartInterrupted) +{ + + bootstrap(); + + librbd::ImageCtx *ictx; + open_local_image(&ictx); + librbd::Journal<>::request_resync(ictx); + close_image(ictx); + + C_SaferCond cond; + m_replayer->start(&cond); + ASSERT_EQ(0, cond.wait()); + + ASSERT_TRUE(m_replayer->is_stopped()); + + C_SaferCond cond2; + m_replayer->start(&cond2); + ASSERT_EQ(0, cond2.wait()); + + ASSERT_EQ(0U, m_watch_handle); + std::string oid = ::journal::Journaler::header_oid(m_remote_image_id); + m_watch_ctx = new C_WatchCtx(this, oid); + ASSERT_EQ(0, m_remote_ioctx.watch2(oid, &m_watch_handle, m_watch_ctx)); + + ASSERT_TRUE(m_replayer->is_replaying()); + + generate_test_data(); + open_remote_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + + wait_for_replay_complete(); + + for (int i = TEST_IO_COUNT; i < 2 * TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + wait_for_replay_complete(); + + open_local_image(&ictx); + for (int i = 0; i < 2 * TEST_IO_COUNT; ++i) { + read_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + + stop(); +} + +TEST_F(TestImageReplayer, MultipleReplayFailures_SingleEpoch) { + bootstrap(); + + // inject a snapshot that cannot be unprotected + librbd::ImageCtx *ictx; + open_image(m_local_ioctx, m_image_name, false, &ictx); + ictx->features &= ~RBD_FEATURE_JOURNALING; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "foo")); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "foo")); + ASSERT_EQ(0, librbd::cls_client::add_child(&ictx->md_ctx, RBD_CHILDREN, + {ictx->md_ctx.get_id(), "", + ictx->id, + ictx->snap_ids[{cls::rbd::UserSnapshotNamespace(), "foo"}]}, + "dummy child id")); + close_image(ictx); + + // race failed op shut down with new ops + open_remote_image(&ictx); + for (uint64_t i = 0; i < 10; ++i) { + RWLock::RLocker owner_locker(ictx->owner_lock); + C_SaferCond request_lock; + ictx->exclusive_lock->acquire_lock(&request_lock); + ASSERT_EQ(0, request_lock.wait()); + + C_SaferCond append_ctx; + ictx->journal->append_op_event( + i, + librbd::journal::EventEntry{ + librbd::journal::SnapUnprotectEvent{i, + cls::rbd::UserSnapshotNamespace(), + "foo"}}, + &append_ctx); + ASSERT_EQ(0, append_ctx.wait()); + + C_SaferCond commit_ctx; + ictx->journal->commit_op_event(i, 0, &commit_ctx); + ASSERT_EQ(0, commit_ctx.wait()); + + C_SaferCond release_ctx; + ictx->exclusive_lock->release_lock(&release_ctx); + ASSERT_EQ(0, release_ctx.wait()); + } + + for (uint64_t i = 0; i < 5; ++i) { + start(); + wait_for_stopped(); + unwatch(); + } + close_image(ictx); +} + +TEST_F(TestImageReplayer, MultipleReplayFailures_MultiEpoch) { + bootstrap(); + + // inject a snapshot that cannot be unprotected + librbd::ImageCtx *ictx; + open_image(m_local_ioctx, m_image_name, false, &ictx); + ictx->features &= ~RBD_FEATURE_JOURNALING; + ASSERT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + "foo")); + ASSERT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + "foo")); + ASSERT_EQ(0, librbd::cls_client::add_child(&ictx->md_ctx, RBD_CHILDREN, + {ictx->md_ctx.get_id(), "", + ictx->id, + ictx->snap_ids[{cls::rbd::UserSnapshotNamespace(), + "foo"}]}, + "dummy child id")); + close_image(ictx); + + // race failed op shut down with new tag flush + open_remote_image(&ictx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + C_SaferCond request_lock; + ictx->exclusive_lock->acquire_lock(&request_lock); + ASSERT_EQ(0, request_lock.wait()); + + C_SaferCond append_ctx; + ictx->journal->append_op_event( + 1U, + librbd::journal::EventEntry{ + librbd::journal::SnapUnprotectEvent{1U, + cls::rbd::UserSnapshotNamespace(), + "foo"}}, + &append_ctx); + ASSERT_EQ(0, append_ctx.wait()); + + C_SaferCond commit_ctx; + ictx->journal->commit_op_event(1U, 0, &commit_ctx); + ASSERT_EQ(0, commit_ctx.wait()); + + C_SaferCond release_ctx; + ictx->exclusive_lock->release_lock(&release_ctx); + ASSERT_EQ(0, release_ctx.wait()); + } + + generate_test_data(); + write_test_data(ictx, m_test_data, 0, TEST_IO_SIZE); + + for (uint64_t i = 0; i < 5; ++i) { + start(); + wait_for_stopped(); + unwatch(); + } + close_image(ictx); +} + +TEST_F(TestImageReplayer, Disconnect) +{ + bootstrap(); + + // Make sure rbd_mirroring_resync_after_disconnect is not set + EXPECT_EQ(0, m_local_cluster->conf_set("rbd_mirroring_resync_after_disconnect", "false")); + + // Test start fails if disconnected + + librbd::ImageCtx *ictx; + + generate_test_data(); + open_remote_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + std::string oid = ::journal::Journaler::header_oid(m_remote_image_id); + ASSERT_EQ(0, cls::journal::client::client_update_state(m_remote_ioctx, oid, + m_local_mirror_uuid, cls::journal::CLIENT_STATE_DISCONNECTED)); + + C_SaferCond cond1; + m_replayer->start(&cond1); + ASSERT_EQ(-ENOTCONN, cond1.wait()); + + // Test start succeeds after resync + + open_local_image(&ictx); + librbd::Journal<>::request_resync(ictx); + close_image(ictx); + C_SaferCond cond2; + m_replayer->start(&cond2); + ASSERT_EQ(0, cond2.wait()); + + start(); + wait_for_replay_complete(); + + // Test replay stopped after disconnect + + open_remote_image(&ictx); + for (int i = TEST_IO_COUNT; i < 2 * TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + ASSERT_EQ(0, cls::journal::client::client_update_state(m_remote_ioctx, oid, + m_local_mirror_uuid, cls::journal::CLIENT_STATE_DISCONNECTED)); + bufferlist bl; + ASSERT_EQ(0, m_remote_ioctx.notify2(oid, bl, 5000, NULL)); + + wait_for_stopped(); + + // Test start fails after disconnect + + C_SaferCond cond3; + m_replayer->start(&cond3); + ASSERT_EQ(-ENOTCONN, cond3.wait()); + C_SaferCond cond4; + m_replayer->start(&cond4); + ASSERT_EQ(-ENOTCONN, cond4.wait()); + + // Test automatic resync if rbd_mirroring_resync_after_disconnect is set + + EXPECT_EQ(0, m_local_cluster->conf_set("rbd_mirroring_resync_after_disconnect", "true")); + + // Resync is flagged on first start attempt + C_SaferCond cond5; + m_replayer->start(&cond5); + ASSERT_EQ(-ENOTCONN, cond5.wait()); + + C_SaferCond cond6; + m_replayer->start(&cond6); + ASSERT_EQ(0, cond6.wait()); + wait_for_replay_complete(); + + stop(); +} + +TEST_F(TestImageReplayer, UpdateFeatures) +{ + const uint64_t FEATURES_TO_UPDATE = + RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF; + + uint64_t features; + librbd::ImageCtx *ictx; + + // Make sure the features we will update are disabled initially + + open_remote_image(&ictx); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + features &= FEATURES_TO_UPDATE; + if (features) { + ASSERT_EQ(0, ictx->operations->update_features(FEATURES_TO_UPDATE, + false)); + } + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0U, features & FEATURES_TO_UPDATE); + close_image(ictx); + + bootstrap(); + + open_remote_image(&ictx); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0U, features & FEATURES_TO_UPDATE); + close_image(ictx); + + open_local_image(&ictx); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0U, features & FEATURES_TO_UPDATE); + close_image(ictx); + + // Start replay and update features + + start(); + + open_remote_image(&ictx); + ASSERT_EQ(0, ictx->operations->update_features(FEATURES_TO_UPDATE, + true)); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(FEATURES_TO_UPDATE, features & FEATURES_TO_UPDATE); + close_image(ictx); + + wait_for_replay_complete(); + + open_local_image(&ictx); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(FEATURES_TO_UPDATE, features & FEATURES_TO_UPDATE); + close_image(ictx); + + open_remote_image(&ictx); + ASSERT_EQ(0, ictx->operations->update_features(FEATURES_TO_UPDATE, + false)); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0U, features & FEATURES_TO_UPDATE); + close_image(ictx); + + wait_for_replay_complete(); + + open_local_image(&ictx); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_EQ(0U, features & FEATURES_TO_UPDATE); + close_image(ictx); + + // Test update_features error does not stop replication + + open_remote_image(&ictx); + ASSERT_EQ(0, librbd::get_features(ictx, &features)); + ASSERT_NE(0U, features & RBD_FEATURE_EXCLUSIVE_LOCK); + ASSERT_EQ(-EINVAL, ictx->operations->update_features(RBD_FEATURE_EXCLUSIVE_LOCK, + false)); + generate_test_data(); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + wait_for_replay_complete(); + + open_local_image(&ictx); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + read_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + + stop(); +} + +TEST_F(TestImageReplayer, MetadataSetRemove) +{ + const std::string KEY = "test_key"; + const std::string VALUE = "test_value"; + + librbd::ImageCtx *ictx; + std::string value; + + bootstrap(); + + start(); + + // Test metadata_set replication + + open_remote_image(&ictx); + ASSERT_EQ(0, ictx->operations->metadata_set(KEY, VALUE)); + value.clear(); + ASSERT_EQ(0, librbd::metadata_get(ictx, KEY, &value)); + ASSERT_EQ(VALUE, value); + close_image(ictx); + + wait_for_replay_complete(); + + open_local_image(&ictx); + value.clear(); + ASSERT_EQ(0, librbd::metadata_get(ictx, KEY, &value)); + ASSERT_EQ(VALUE, value); + close_image(ictx); + + // Test metadata_remove replication + + open_remote_image(&ictx); + ASSERT_EQ(0, ictx->operations->metadata_remove(KEY)); + ASSERT_EQ(-ENOENT, librbd::metadata_get(ictx, KEY, &value)); + close_image(ictx); + + wait_for_replay_complete(); + + open_local_image(&ictx); + ASSERT_EQ(-ENOENT, librbd::metadata_get(ictx, KEY, &value)); + close_image(ictx); + + stop(); +} + +TEST_F(TestImageReplayer, MirroringDelay) +{ + const double DELAY = 10; // set less than wait_for_replay_complete timeout + + librbd::ImageCtx *ictx; + utime_t start_time; + double delay; + + bootstrap(); + + ASSERT_EQ(0, m_local_cluster->conf_set("rbd_mirroring_replay_delay", + stringify(DELAY).c_str())); + open_local_image(&ictx); + ASSERT_EQ(DELAY, ictx->mirroring_replay_delay); + close_image(ictx); + + start(); + + // Test delay + + generate_test_data(); + open_remote_image(&ictx); + start_time = ceph_clock_now(); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + flush(ictx); + close_image(ictx); + + wait_for_replay_complete(); + delay = ceph_clock_now() - start_time; + ASSERT_GE(delay, DELAY); + + // Test stop when delaying replay + + open_remote_image(&ictx); + start_time = ceph_clock_now(); + for (int i = 0; i < TEST_IO_COUNT; ++i) { + write_test_data(ictx, m_test_data, TEST_IO_SIZE * i, TEST_IO_SIZE); + } + close_image(ictx); + + sleep(DELAY / 2); + stop(); + start(); + + wait_for_replay_complete(); + delay = ceph_clock_now() - start_time; + ASSERT_GE(delay, DELAY); + + stop(); +} diff --git a/src/test/rbd_mirror/test_ImageSync.cc b/src/test/rbd_mirror/test_ImageSync.cc new file mode 100644 index 00000000..7f9ae105 --- /dev/null +++ b/src/test/rbd_mirror/test_ImageSync.cc @@ -0,0 +1,347 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/rbd_mirror/test_fixture.h" +#include "include/stringify.h" +#include "include/rbd/librbd.hpp" +#include "journal/Journaler.h" +#include "journal/Settings.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/Operations.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageDispatchSpec.h" +#include "librbd/io/ImageRequestWQ.h" +#include "librbd/io/ReadResult.h" +#include "librbd/journal/Types.h" +#include "tools/rbd_mirror/ImageSync.h" +#include "tools/rbd_mirror/InstanceWatcher.h" +#include "tools/rbd_mirror/Threads.h" + +void register_test_image_sync() { +} + +namespace rbd { +namespace mirror { + +namespace { + +int flush(librbd::ImageCtx *image_ctx) { + C_SaferCond ctx; + auto aio_comp = librbd::io::AioCompletion::create_and_start( + &ctx, image_ctx, librbd::io::AIO_TYPE_FLUSH); + auto req = librbd::io::ImageDispatchSpec<>::create_flush_request( + *image_ctx, aio_comp, librbd::io::FLUSH_SOURCE_INTERNAL, {}); + req->send(); + delete req; + return ctx.wait(); +} + +void scribble(librbd::ImageCtx *image_ctx, int num_ops, uint64_t max_size) +{ + max_size = std::min(image_ctx->size, max_size); + for (int i=0; isize - max_size + 1); + uint64_t len = 1 + rand() % max_size; + + if (rand() % 4 == 0) { + ASSERT_EQ((int)len, + image_ctx->io_work_queue->discard( + off, len, image_ctx->discard_granularity_bytes)); + } else { + bufferlist bl; + bl.append(std::string(len, '1')); + ASSERT_EQ((int)len, image_ctx->io_work_queue->write(off, len, + std::move(bl), 0)); + } + } + + RWLock::RLocker owner_locker(image_ctx->owner_lock); + ASSERT_EQ(0, flush(image_ctx)); +} + +} // anonymous namespace +class TestImageSync : public TestFixture { +public: + + void SetUp() override { + TestFixture::SetUp(); + create_and_open(m_local_io_ctx, &m_local_image_ctx); + create_and_open(m_remote_io_ctx, &m_remote_image_ctx); + + m_instance_watcher = rbd::mirror::InstanceWatcher<>::create( + m_local_io_ctx, m_threads->work_queue, nullptr); + m_instance_watcher->handle_acquire_leader(); + + m_remote_journaler = new ::journal::Journaler( + m_threads->work_queue, m_threads->timer, &m_threads->timer_lock, + m_remote_io_ctx, m_remote_image_ctx->id, "mirror-uuid", {}); + + m_client_meta = {"image-id"}; + + librbd::journal::ClientData client_data(m_client_meta); + bufferlist client_data_bl; + encode(client_data, client_data_bl); + + ASSERT_EQ(0, m_remote_journaler->register_client(client_data_bl)); + } + + void TearDown() override { + TestFixture::TearDown(); + + m_instance_watcher->handle_release_leader(); + + delete m_remote_journaler; + delete m_instance_watcher; + } + + void create_and_open(librados::IoCtx &io_ctx, librbd::ImageCtx **image_ctx) { + librbd::RBD rbd; + ASSERT_EQ(0, create_image(rbd, io_ctx, m_image_name, m_image_size)); + ASSERT_EQ(0, open_image(io_ctx, m_image_name, image_ctx)); + + C_SaferCond ctx; + { + RWLock::RLocker owner_locker((*image_ctx)->owner_lock); + (*image_ctx)->exclusive_lock->try_acquire_lock(&ctx); + } + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE((*image_ctx)->exclusive_lock->is_lock_owner()); + } + + ImageSync<> *create_request(Context *ctx) { + return new ImageSync<>(m_local_image_ctx, m_remote_image_ctx, + m_threads->timer, &m_threads->timer_lock, + "mirror-uuid", m_remote_journaler, &m_client_meta, + m_threads->work_queue, m_instance_watcher, ctx); + } + + librbd::ImageCtx *m_remote_image_ctx; + librbd::ImageCtx *m_local_image_ctx; + rbd::mirror::InstanceWatcher<> *m_instance_watcher; + ::journal::Journaler *m_remote_journaler; + librbd::journal::MirrorPeerClientMeta m_client_meta; +}; + +TEST_F(TestImageSync, Empty) { + C_SaferCond ctx; + ImageSync<> *request = create_request(&ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(0U, m_client_meta.sync_points.size()); + ASSERT_EQ(0, m_remote_image_ctx->state->refresh()); + ASSERT_EQ(0U, m_remote_image_ctx->snap_ids.size()); + ASSERT_EQ(0, m_local_image_ctx->state->refresh()); + ASSERT_EQ(1U, m_local_image_ctx->snap_ids.size()); // deleted on journal replay +} + +TEST_F(TestImageSync, Simple) { + scribble(m_remote_image_ctx, 10, 102400); + + C_SaferCond ctx; + ImageSync<> *request = create_request(&ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + int64_t object_size = std::min( + m_remote_image_ctx->size, 1 << m_remote_image_ctx->order); + bufferlist read_remote_bl; + read_remote_bl.append(std::string(object_size, '1')); + bufferlist read_local_bl; + read_local_bl.append(std::string(object_size, '1')); + + for (uint64_t offset = 0; offset < m_remote_image_ctx->size; + offset += object_size) { + ASSERT_LE(0, m_remote_image_ctx->io_work_queue->read( + offset, object_size, + librbd::io::ReadResult{&read_remote_bl}, 0)); + ASSERT_LE(0, m_local_image_ctx->io_work_queue->read( + offset, object_size, + librbd::io::ReadResult{&read_local_bl}, 0)); + ASSERT_TRUE(read_remote_bl.contents_equal(read_local_bl)); + } +} + +TEST_F(TestImageSync, Resize) { + int64_t object_size = std::min( + m_remote_image_ctx->size, 1 << m_remote_image_ctx->order); + + uint64_t off = 0; + uint64_t len = object_size / 10; + + bufferlist bl; + bl.append(std::string(len, '1')); + ASSERT_EQ((int)len, m_remote_image_ctx->io_work_queue->write(off, len, + std::move(bl), + 0)); + { + RWLock::RLocker owner_locker(m_remote_image_ctx->owner_lock); + ASSERT_EQ(0, flush(m_remote_image_ctx)); + } + + ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap", nullptr)); + + uint64_t size = object_size - 1; + librbd::NoOpProgressContext no_op_progress_ctx; + ASSERT_EQ(0, m_remote_image_ctx->operations->resize(size, true, + no_op_progress_ctx)); + + C_SaferCond ctx; + ImageSync<> *request = create_request(&ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + bufferlist read_remote_bl; + read_remote_bl.append(std::string(len, '\0')); + bufferlist read_local_bl; + read_local_bl.append(std::string(len, '\0')); + + ASSERT_LE(0, m_remote_image_ctx->io_work_queue->read( + off, len, librbd::io::ReadResult{&read_remote_bl}, 0)); + ASSERT_LE(0, m_local_image_ctx->io_work_queue->read( + off, len, librbd::io::ReadResult{&read_local_bl}, 0)); + + ASSERT_TRUE(read_remote_bl.contents_equal(read_local_bl)); +} + +TEST_F(TestImageSync, Discard) { + int64_t object_size = std::min( + m_remote_image_ctx->size, 1 << m_remote_image_ctx->order); + + uint64_t off = 0; + uint64_t len = object_size / 10; + + bufferlist bl; + bl.append(std::string(len, '1')); + ASSERT_EQ((int)len, m_remote_image_ctx->io_work_queue->write(off, len, + std::move(bl), + 0)); + { + RWLock::RLocker owner_locker(m_remote_image_ctx->owner_lock); + ASSERT_EQ(0, flush(m_remote_image_ctx)); + } + + ASSERT_EQ(0, create_snap(m_remote_image_ctx, "snap", nullptr)); + + ASSERT_EQ((int)len - 2, + m_remote_image_ctx->io_work_queue->discard( + off + 1, len - 2, m_remote_image_ctx->discard_granularity_bytes)); + { + RWLock::RLocker owner_locker(m_remote_image_ctx->owner_lock); + ASSERT_EQ(0, flush(m_remote_image_ctx)); + } + + C_SaferCond ctx; + ImageSync<> *request = create_request(&ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + bufferlist read_remote_bl; + read_remote_bl.append(std::string(object_size, '\0')); + bufferlist read_local_bl; + read_local_bl.append(std::string(object_size, '\0')); + + ASSERT_LE(0, m_remote_image_ctx->io_work_queue->read( + off, len, librbd::io::ReadResult{&read_remote_bl}, 0)); + ASSERT_LE(0, m_local_image_ctx->io_work_queue->read( + off, len, librbd::io::ReadResult{&read_local_bl}, 0)); + + ASSERT_TRUE(read_remote_bl.contents_equal(read_local_bl)); +} + +TEST_F(TestImageSync, SnapshotStress) { + std::list snap_names; + + const int num_snaps = 4; + for (int idx = 0; idx <= num_snaps; ++idx) { + scribble(m_remote_image_ctx, 10, 102400); + + librbd::NoOpProgressContext no_op_progress_ctx; + uint64_t size = 1 + rand() % m_image_size; + ASSERT_EQ(0, m_remote_image_ctx->operations->resize(size, true, + no_op_progress_ctx)); + ASSERT_EQ(0, m_remote_image_ctx->state->refresh()); + + if (idx < num_snaps) { + snap_names.push_back("snap" + stringify(idx + 1)); + ASSERT_EQ(0, create_snap(m_remote_image_ctx, snap_names.back().c_str(), + nullptr)); + } else { + snap_names.push_back(""); + } + } + + C_SaferCond ctx; + ImageSync<> *request = create_request(&ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); + + int64_t object_size = std::min( + m_remote_image_ctx->size, 1 << m_remote_image_ctx->order); + bufferlist read_remote_bl; + read_remote_bl.append(std::string(object_size, '1')); + bufferlist read_local_bl; + read_local_bl.append(std::string(object_size, '1')); + + for (auto &snap_name : snap_names) { + uint64_t remote_snap_id; + { + RWLock::RLocker remote_snap_locker(m_remote_image_ctx->snap_lock); + remote_snap_id = m_remote_image_ctx->get_snap_id( + cls::rbd::UserSnapshotNamespace{}, snap_name); + } + + uint64_t remote_size; + { + C_SaferCond ctx; + m_remote_image_ctx->state->snap_set(remote_snap_id, &ctx); + ASSERT_EQ(0, ctx.wait()); + + RWLock::RLocker remote_snap_locker(m_remote_image_ctx->snap_lock); + remote_size = m_remote_image_ctx->get_image_size( + m_remote_image_ctx->snap_id); + } + + uint64_t local_snap_id; + { + RWLock::RLocker snap_locker(m_local_image_ctx->snap_lock); + local_snap_id = m_local_image_ctx->get_snap_id( + cls::rbd::UserSnapshotNamespace{}, snap_name); + } + + uint64_t local_size; + { + C_SaferCond ctx; + m_local_image_ctx->state->snap_set(local_snap_id, &ctx); + ASSERT_EQ(0, ctx.wait()); + + RWLock::RLocker snap_locker(m_local_image_ctx->snap_lock); + local_size = m_local_image_ctx->get_image_size( + m_local_image_ctx->snap_id); + bool flags_set; + ASSERT_EQ(0, m_local_image_ctx->test_flags(m_local_image_ctx->snap_id, + RBD_FLAG_OBJECT_MAP_INVALID, + m_local_image_ctx->snap_lock, + &flags_set)); + ASSERT_FALSE(flags_set); + } + + ASSERT_EQ(remote_size, local_size); + + for (uint64_t offset = 0; offset < remote_size; offset += object_size) { + ASSERT_LE(0, m_remote_image_ctx->io_work_queue->read( + offset, object_size, + librbd::io::ReadResult{&read_remote_bl}, 0)); + ASSERT_LE(0, m_local_image_ctx->io_work_queue->read( + offset, object_size, + librbd::io::ReadResult{&read_local_bl}, 0)); + ASSERT_TRUE(read_remote_bl.contents_equal(read_local_bl)); + } + } +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_InstanceWatcher.cc b/src/test/rbd_mirror/test_InstanceWatcher.cc new file mode 100644 index 00000000..92a8f943 --- /dev/null +++ b/src/test/rbd_mirror/test_InstanceWatcher.cc @@ -0,0 +1,132 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/rados/librados.hpp" +#include "include/stringify.h" +#include "cls/rbd/cls_rbd_types.h" +#include "cls/rbd/cls_rbd_client.h" +#include "librbd/Utils.h" +#include "librbd/internal.h" +#include "test/rbd_mirror/test_fixture.h" +#include "tools/rbd_mirror/InstanceWatcher.h" +#include "tools/rbd_mirror/Threads.h" +#include "common/Cond.h" + +#include "test/librados/test_cxx.h" +#include "gtest/gtest.h" + +using rbd::mirror::InstanceWatcher; + +void register_test_instance_watcher() { +} + +class TestInstanceWatcher : public ::rbd::mirror::TestFixture { +public: + std::string m_instance_id; + std::string m_oid; + + void SetUp() override { + TestFixture::SetUp(); + m_local_io_ctx.remove(RBD_MIRROR_LEADER); + EXPECT_EQ(0, m_local_io_ctx.create(RBD_MIRROR_LEADER, true)); + + m_instance_id = stringify(m_local_io_ctx.get_instance_id()); + m_oid = RBD_MIRROR_INSTANCE_PREFIX + m_instance_id; + } + + void get_instances(std::vector *instance_ids) { + instance_ids->clear(); + C_SaferCond on_get; + InstanceWatcher<>::get_instances(m_local_io_ctx, instance_ids, &on_get); + EXPECT_EQ(0, on_get.wait()); + } +}; + +TEST_F(TestInstanceWatcher, InitShutdown) +{ + InstanceWatcher<> instance_watcher(m_local_io_ctx, m_threads->work_queue, + nullptr, m_instance_id); + std::vector instance_ids; + get_instances(&instance_ids); + ASSERT_EQ(0U, instance_ids.size()); + + uint64_t size; + ASSERT_EQ(-ENOENT, m_local_io_ctx.stat(m_oid, &size, nullptr)); + + // Init + ASSERT_EQ(0, instance_watcher.init()); + + get_instances(&instance_ids); + ASSERT_EQ(1U, instance_ids.size()); + ASSERT_EQ(m_instance_id, instance_ids[0]); + + ASSERT_EQ(0, m_local_io_ctx.stat(m_oid, &size, nullptr)); + std::list watchers; + ASSERT_EQ(0, m_local_io_ctx.list_watchers(m_oid, &watchers)); + ASSERT_EQ(1U, watchers.size()); + ASSERT_EQ(m_instance_id, stringify(watchers.begin()->watcher_id)); + + get_instances(&instance_ids); + ASSERT_EQ(1U, instance_ids.size()); + + // Shutdown + instance_watcher.shut_down(); + + ASSERT_EQ(-ENOENT, m_local_io_ctx.stat(m_oid, &size, nullptr)); + get_instances(&instance_ids); + ASSERT_EQ(0U, instance_ids.size()); +} + +TEST_F(TestInstanceWatcher, Remove) +{ + std::string instance_id = "instance_id"; + std::string oid = RBD_MIRROR_INSTANCE_PREFIX + instance_id; + + std::vector instance_ids; + get_instances(&instance_ids); + ASSERT_EQ(0U, instance_ids.size()); + + uint64_t size; + ASSERT_EQ(-ENOENT, m_local_io_ctx.stat(oid, &size, nullptr)); + + librados::Rados cluster; + librados::IoCtx io_ctx; + ASSERT_EQ("", connect_cluster_pp(cluster)); + ASSERT_EQ(0, cluster.ioctx_create(_local_pool_name.c_str(), io_ctx)); + InstanceWatcher<> instance_watcher(m_local_io_ctx, m_threads->work_queue, + nullptr, "instance_id"); + // Init + ASSERT_EQ(0, instance_watcher.init()); + + get_instances(&instance_ids); + ASSERT_EQ(1U, instance_ids.size()); + ASSERT_EQ(instance_id, instance_ids[0]); + + ASSERT_EQ(0, m_local_io_ctx.stat(oid, &size, nullptr)); + std::list watchers; + ASSERT_EQ(0, m_local_io_ctx.list_watchers(oid, &watchers)); + ASSERT_EQ(1U, watchers.size()); + + // Remove + C_SaferCond on_remove; + InstanceWatcher<>::remove_instance(m_local_io_ctx, m_threads->work_queue, + "instance_id", &on_remove); + ASSERT_EQ(0, on_remove.wait()); + + ASSERT_EQ(-ENOENT, m_local_io_ctx.stat(oid, &size, nullptr)); + get_instances(&instance_ids); + ASSERT_EQ(0U, instance_ids.size()); + + // Shutdown + instance_watcher.shut_down(); + + ASSERT_EQ(-ENOENT, m_local_io_ctx.stat(m_oid, &size, nullptr)); + get_instances(&instance_ids); + ASSERT_EQ(0U, instance_ids.size()); + + // Remove NOENT + C_SaferCond on_remove_noent; + InstanceWatcher<>::remove_instance(m_local_io_ctx, m_threads->work_queue, + instance_id, &on_remove_noent); + ASSERT_EQ(0, on_remove_noent.wait()); +} diff --git a/src/test/rbd_mirror/test_Instances.cc b/src/test/rbd_mirror/test_Instances.cc new file mode 100644 index 00000000..c4e8bd30 --- /dev/null +++ b/src/test/rbd_mirror/test_Instances.cc @@ -0,0 +1,165 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/rados/librados.hpp" +#include "cls/rbd/cls_rbd_client.h" +#include "test/rbd_mirror/test_fixture.h" +#include "tools/rbd_mirror/InstanceWatcher.h" +#include "tools/rbd_mirror/Instances.h" +#include "tools/rbd_mirror/Threads.h" +#include "common/Cond.h" + +#include "test/librados/test.h" +#include "gtest/gtest.h" +#include + +using rbd::mirror::InstanceWatcher; +using rbd::mirror::Instances; + +void register_test_instances() { +} + +class TestInstances : public ::rbd::mirror::TestFixture { +public: + struct Listener : public rbd::mirror::instances::Listener { + std::mutex lock; + + struct Instance { + uint32_t count = 0; + std::set ids; + C_SaferCond ctx; + }; + + Instance add; + Instance remove; + + void handle(const InstanceIds& instance_ids, Instance* instance) { + std::unique_lock locker(lock); + for (auto& instance_id : instance_ids) { + ceph_assert(instance->count > 0); + --instance->count; + + instance->ids.insert(instance_id); + if (instance->count == 0) { + instance->ctx.complete(0); + } + } + } + + void handle_added(const InstanceIds& instance_ids) override { + handle(instance_ids, &add); + } + + void handle_removed(const InstanceIds& instance_ids) override { + handle(instance_ids, &remove); + } + }; + + virtual void SetUp() { + TestFixture::SetUp(); + m_local_io_ctx.remove(RBD_MIRROR_LEADER); + EXPECT_EQ(0, m_local_io_ctx.create(RBD_MIRROR_LEADER, true)); + + m_instance_id = stringify(m_local_io_ctx.get_instance_id()); + } + + Listener m_listener; + std::string m_instance_id; +}; + +TEST_F(TestInstances, InitShutdown) +{ + m_listener.add.count = 1; + Instances<> instances(m_threads, m_local_io_ctx, m_instance_id, m_listener); + + std::string instance_id = "instance_id"; + ASSERT_EQ(0, librbd::cls_client::mirror_instances_add(&m_local_io_ctx, + instance_id)); + + C_SaferCond on_init; + instances.init(&on_init); + ASSERT_EQ(0, on_init.wait()); + + ASSERT_LT(0U, m_listener.add.count); + instances.unblock_listener(); + + ASSERT_EQ(0, m_listener.add.ctx.wait()); + ASSERT_EQ(std::set({instance_id}), m_listener.add.ids); + + C_SaferCond on_shut_down; + instances.shut_down(&on_shut_down); + ASSERT_EQ(0, on_shut_down.wait()); +} + +TEST_F(TestInstances, InitEnoent) +{ + Instances<> instances(m_threads, m_local_io_ctx, m_instance_id, m_listener); + + m_local_io_ctx.remove(RBD_MIRROR_LEADER); + + C_SaferCond on_init; + instances.init(&on_init); + ASSERT_EQ(0, on_init.wait()); + + C_SaferCond on_shut_down; + instances.shut_down(&on_shut_down); + ASSERT_EQ(0, on_shut_down.wait()); +} + +TEST_F(TestInstances, NotifyRemove) +{ + // speed testing up a little + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_leader_heartbeat_interval", "1")); + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_leader_max_missed_heartbeats", + "2")); + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_leader_max_acquire_attempts_before_break", + "0")); + + m_listener.add.count = 2; + m_listener.remove.count = 1; + Instances<> instances(m_threads, m_local_io_ctx, m_instance_id, m_listener); + + std::string instance_id1 = "instance_id1"; + std::string instance_id2 = "instance_id2"; + + ASSERT_EQ(0, librbd::cls_client::mirror_instances_add(&m_local_io_ctx, + instance_id1)); + + + C_SaferCond on_init; + instances.init(&on_init); + ASSERT_EQ(0, on_init.wait()); + + instances.acked({instance_id1, instance_id2}); + + ASSERT_LT(0U, m_listener.add.count); + instances.unblock_listener(); + + ASSERT_EQ(0, m_listener.add.ctx.wait()); + ASSERT_EQ(std::set({instance_id1, instance_id2}), + m_listener.add.ids); + + std::vector instance_ids; + for (int i = 0; i < 100; i++) { + instances.acked({instance_id1}); + if (m_listener.remove.count > 0) { + usleep(250000); + } + } + + instances.acked({instance_id1}); + ASSERT_EQ(0, m_listener.remove.ctx.wait()); + ASSERT_EQ(std::set({instance_id2}), + m_listener.remove.ids); + + C_SaferCond on_get; + instances.acked({instance_id1}); + InstanceWatcher<>::get_instances(m_local_io_ctx, &instance_ids, &on_get); + EXPECT_EQ(0, on_get.wait()); + EXPECT_EQ(1U, instance_ids.size()); + ASSERT_EQ(instance_ids[0], instance_id1); + + C_SaferCond on_shut_down; + instances.shut_down(&on_shut_down); + ASSERT_EQ(0, on_shut_down.wait()); +} diff --git a/src/test/rbd_mirror/test_LeaderWatcher.cc b/src/test/rbd_mirror/test_LeaderWatcher.cc new file mode 100644 index 00000000..8a5cd890 --- /dev/null +++ b/src/test/rbd_mirror/test_LeaderWatcher.cc @@ -0,0 +1,317 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/rados/librados.hpp" +#include "librbd/internal.h" +#include "librbd/Utils.h" +#include "librbd/api/Mirror.h" +#include "test/librbd/test_support.h" +#include "test/rbd_mirror/test_fixture.h" +#include "tools/rbd_mirror/LeaderWatcher.h" +#include "tools/rbd_mirror/Threads.h" +#include "common/Cond.h" + +#include "test/librados/test_cxx.h" +#include "gtest/gtest.h" + +using librbd::util::unique_lock_name; +using rbd::mirror::LeaderWatcher; + +void register_test_leader_watcher() { +} + +class TestLeaderWatcher : public ::rbd::mirror::TestFixture { +public: + class Listener : public rbd::mirror::leader_watcher::Listener { + public: + Listener() + : m_test_lock(unique_lock_name("LeaderWatcher::m_test_lock", this)) { + } + + void on_acquire(int r, Context *ctx) { + Mutex::Locker locker(m_test_lock); + m_on_acquire_r = r; + m_on_acquire = ctx; + } + + void on_release(int r, Context *ctx) { + Mutex::Locker locker(m_test_lock); + m_on_release_r = r; + m_on_release = ctx; + } + + int acquire_count() const { + Mutex::Locker locker(m_test_lock); + return m_acquire_count; + } + + int release_count() const { + Mutex::Locker locker(m_test_lock); + return m_release_count; + } + + void post_acquire_handler(Context *on_finish) override { + Mutex::Locker locker(m_test_lock); + m_acquire_count++; + on_finish->complete(m_on_acquire_r); + m_on_acquire_r = 0; + if (m_on_acquire != nullptr) { + m_on_acquire->complete(0); + m_on_acquire = nullptr; + } + } + + void pre_release_handler(Context *on_finish) override { + Mutex::Locker locker(m_test_lock); + m_release_count++; + on_finish->complete(m_on_release_r); + m_on_release_r = 0; + if (m_on_release != nullptr) { + m_on_release->complete(0); + m_on_release = nullptr; + } + } + + void update_leader_handler(const std::string &leader_instance_id) override { + } + + void handle_instances_added(const InstanceIds& instance_ids) override { + } + void handle_instances_removed(const InstanceIds& instance_ids) override { + } + + private: + mutable Mutex m_test_lock; + int m_acquire_count = 0; + int m_release_count = 0; + int m_on_acquire_r = 0; + int m_on_release_r = 0; + Context *m_on_acquire = nullptr; + Context *m_on_release = nullptr; + }; + + struct Connection { + librados::Rados cluster; + librados::IoCtx io_ctx; + }; + + std::list > m_connections; + + void SetUp() override { + TestFixture::SetUp(); + EXPECT_EQ(0, librbd::api::Mirror<>::mode_set(m_local_io_ctx, + RBD_MIRROR_MODE_POOL)); + + if (is_librados_test_stub(*_rados)) { + // speed testing up a little + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_leader_heartbeat_interval", + "1")); + } + } + + librados::IoCtx &create_connection(bool no_heartbeats = false) { + m_connections.push_back(std::unique_ptr(new Connection())); + Connection *c = m_connections.back().get(); + + EXPECT_EQ("", connect_cluster_pp(c->cluster)); + if (no_heartbeats) { + EXPECT_EQ(0, c->cluster.conf_set("rbd_mirror_leader_heartbeat_interval", + "3600")); + } else if (is_librados_test_stub(*_rados)) { + EXPECT_EQ(0, c->cluster.conf_set("rbd_mirror_leader_heartbeat_interval", + "1")); + } + EXPECT_EQ(0, c->cluster.ioctx_create(_local_pool_name.c_str(), c->io_ctx)); + + return c->io_ctx; + } +}; + +TEST_F(TestLeaderWatcher, InitShutdown) +{ + Listener listener; + LeaderWatcher<> leader_watcher(m_threads, m_local_io_ctx, &listener); + + C_SaferCond on_init_acquire; + listener.on_acquire(0, &on_init_acquire); + ASSERT_EQ(0, leader_watcher.init()); + ASSERT_EQ(0, on_init_acquire.wait()); + ASSERT_TRUE(leader_watcher.is_leader()); + + leader_watcher.shut_down(); + ASSERT_EQ(1, listener.acquire_count()); + ASSERT_EQ(1, listener.release_count()); + ASSERT_FALSE(leader_watcher.is_leader()); +} + +TEST_F(TestLeaderWatcher, Release) +{ + Listener listener; + LeaderWatcher<> leader_watcher(m_threads, m_local_io_ctx, &listener); + + C_SaferCond on_init_acquire; + listener.on_acquire(0, &on_init_acquire); + ASSERT_EQ(0, leader_watcher.init()); + ASSERT_EQ(0, on_init_acquire.wait()); + ASSERT_TRUE(leader_watcher.is_leader()); + + C_SaferCond on_release; + C_SaferCond on_acquire; + listener.on_release(0, &on_release); + listener.on_acquire(0, &on_acquire); + leader_watcher.release_leader(); + ASSERT_EQ(0, on_release.wait()); + ASSERT_FALSE(leader_watcher.is_leader()); + + // wait for lock re-acquired due to no another locker + ASSERT_EQ(0, on_acquire.wait()); + ASSERT_TRUE(leader_watcher.is_leader()); + + C_SaferCond on_release2; + listener.on_release(0, &on_release2); + leader_watcher.release_leader(); + ASSERT_EQ(0, on_release2.wait()); + + leader_watcher.shut_down(); + ASSERT_EQ(2, listener.acquire_count()); + ASSERT_EQ(2, listener.release_count()); +} + +TEST_F(TestLeaderWatcher, ListenerError) +{ + Listener listener; + LeaderWatcher<> leader_watcher(m_threads, m_local_io_ctx, &listener); + + // make listener return error on acquire + C_SaferCond on_init_acquire, on_init_release; + listener.on_acquire(-EINVAL, &on_init_acquire); + listener.on_release(0, &on_init_release); + ASSERT_EQ(0, leader_watcher.init()); + ASSERT_EQ(0, on_init_acquire.wait()); + ASSERT_EQ(0, on_init_release.wait()); + ASSERT_FALSE(leader_watcher.is_leader()); + + // wait for lock re-acquired due to no another locker + C_SaferCond on_acquire; + listener.on_acquire(0, &on_acquire); + ASSERT_EQ(0, on_acquire.wait()); + ASSERT_TRUE(leader_watcher.is_leader()); + + // make listener return error on release + C_SaferCond on_release; + listener.on_release(-EINVAL, &on_release); + leader_watcher.release_leader(); + ASSERT_EQ(0, on_release.wait()); + ASSERT_FALSE(leader_watcher.is_leader()); + + leader_watcher.shut_down(); + ASSERT_EQ(2, listener.acquire_count()); + ASSERT_EQ(2, listener.release_count()); + ASSERT_FALSE(leader_watcher.is_leader()); +} + +TEST_F(TestLeaderWatcher, Two) +{ + Listener listener1; + LeaderWatcher<> leader_watcher1(m_threads, create_connection(), &listener1); + + C_SaferCond on_init_acquire; + listener1.on_acquire(0, &on_init_acquire); + ASSERT_EQ(0, leader_watcher1.init()); + ASSERT_EQ(0, on_init_acquire.wait()); + + Listener listener2; + LeaderWatcher<> leader_watcher2(m_threads, create_connection(), &listener2); + + ASSERT_EQ(0, leader_watcher2.init()); + ASSERT_TRUE(leader_watcher1.is_leader()); + ASSERT_FALSE(leader_watcher2.is_leader()); + + C_SaferCond on_release; + C_SaferCond on_acquire; + listener1.on_release(0, &on_release); + listener2.on_acquire(0, &on_acquire); + leader_watcher1.release_leader(); + ASSERT_EQ(0, on_release.wait()); + ASSERT_FALSE(leader_watcher1.is_leader()); + + // wait for lock acquired by another watcher + ASSERT_EQ(0, on_acquire.wait()); + ASSERT_TRUE(leader_watcher2.is_leader()); + + leader_watcher1.shut_down(); + leader_watcher2.shut_down(); + + ASSERT_EQ(1, listener1.acquire_count()); + ASSERT_EQ(1, listener1.release_count()); + ASSERT_EQ(1, listener2.acquire_count()); + ASSERT_EQ(1, listener2.release_count()); +} + +TEST_F(TestLeaderWatcher, Break) +{ + Listener listener1, listener2; + LeaderWatcher<> leader_watcher1(m_threads, + create_connection(true /* no heartbeats */), + &listener1); + LeaderWatcher<> leader_watcher2(m_threads, create_connection(), &listener2); + + C_SaferCond on_init_acquire; + listener1.on_acquire(0, &on_init_acquire); + ASSERT_EQ(0, leader_watcher1.init()); + ASSERT_EQ(0, on_init_acquire.wait()); + + C_SaferCond on_acquire; + listener2.on_acquire(0, &on_acquire); + ASSERT_EQ(0, leader_watcher2.init()); + ASSERT_FALSE(leader_watcher2.is_leader()); + + // wait for lock broken due to no heartbeats and re-acquired + ASSERT_EQ(0, on_acquire.wait()); + ASSERT_TRUE(leader_watcher2.is_leader()); + + leader_watcher1.shut_down(); + leader_watcher2.shut_down(); +} + +TEST_F(TestLeaderWatcher, Stress) +{ + const int WATCHERS_COUNT = 20; + std::list *> leader_watchers; + Listener listener; + + for (int i = 0; i < WATCHERS_COUNT; i++) { + auto leader_watcher = + new LeaderWatcher<>(m_threads, create_connection(), &listener); + leader_watchers.push_back(leader_watcher); + } + + C_SaferCond on_init_acquire; + listener.on_acquire(0, &on_init_acquire); + for (auto &leader_watcher : leader_watchers) { + ASSERT_EQ(0, leader_watcher->init()); + } + ASSERT_EQ(0, on_init_acquire.wait()); + + while (true) { + C_SaferCond on_acquire; + listener.on_acquire(0, &on_acquire); + std::unique_ptr > leader_watcher; + for (auto it = leader_watchers.begin(); it != leader_watchers.end(); ) { + if ((*it)->is_leader()) { + ASSERT_FALSE(leader_watcher); + leader_watcher.reset(*it); + it = leader_watchers.erase(it); + } else { + it++; + } + } + + ASSERT_TRUE(leader_watcher); + leader_watcher->shut_down(); + if (leader_watchers.empty()) { + break; + } + ASSERT_EQ(0, on_acquire.wait()); + } +} diff --git a/src/test/rbd_mirror/test_PoolWatcher.cc b/src/test/rbd_mirror/test_PoolWatcher.cc new file mode 100644 index 00000000..108dc355 --- /dev/null +++ b/src/test/rbd_mirror/test_PoolWatcher.cc @@ -0,0 +1,254 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/rados/librados.hpp" +#include "include/rbd/librbd.hpp" +#include "include/stringify.h" +#include "test/rbd_mirror/test_fixture.h" +#include "cls/rbd/cls_rbd_types.h" +#include "cls/rbd/cls_rbd_client.h" +#include "include/rbd_types.h" +#include "librbd/internal.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/Utils.h" +#include "librbd/api/Mirror.h" +#include "common/Cond.h" +#include "common/errno.h" +#include "common/Mutex.h" +#include "tools/rbd_mirror/PoolWatcher.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/Types.h" +#include "tools/rbd_mirror/pool_watcher/Types.h" +#include "test/librados/test_cxx.h" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +using rbd::mirror::ImageId; +using rbd::mirror::ImageIds; +using rbd::mirror::PoolWatcher; +using rbd::mirror::PeerSpec; +using rbd::mirror::RadosRef; +using std::map; +using std::set; +using std::string; + +void register_test_pool_watcher() { +} + +class TestPoolWatcher : public ::rbd::mirror::TestFixture { +public: + + TestPoolWatcher() + : m_lock("TestPoolWatcherLock"), m_pool_watcher_listener(this), + m_image_number(0), m_snap_number(0) + { + m_cluster = std::make_shared(); + EXPECT_EQ("", connect_cluster_pp(*m_cluster)); + } + + void TearDown() override { + if (m_pool_watcher) { + C_SaferCond ctx; + m_pool_watcher->shut_down(&ctx); + EXPECT_EQ(0, ctx.wait()); + } + + m_cluster->wait_for_latest_osdmap(); + for (auto& pool : m_pools) { + EXPECT_EQ(0, m_cluster->pool_delete(pool.c_str())); + } + + TestFixture::TearDown(); + } + + struct PoolWatcherListener : public rbd::mirror::pool_watcher::Listener { + TestPoolWatcher *test; + Cond cond; + ImageIds image_ids; + + explicit PoolWatcherListener(TestPoolWatcher *test) : test(test) { + } + + void handle_update(const std::string &mirror_uuid, + ImageIds &&added_image_ids, + ImageIds &&removed_image_ids) override { + Mutex::Locker locker(test->m_lock); + for (auto &image_id : removed_image_ids) { + image_ids.erase(image_id); + } + image_ids.insert(added_image_ids.begin(), added_image_ids.end()); + cond.Signal(); + } + }; + + void create_pool(bool enable_mirroring, const PeerSpec &peer, string *name=nullptr) { + string pool_name = get_temp_pool_name("test-rbd-mirror-"); + ASSERT_EQ(0, m_cluster->pool_create(pool_name.c_str())); + + int64_t pool_id = m_cluster->pool_lookup(pool_name.c_str()); + ASSERT_GE(pool_id, 0); + m_pools.insert(pool_name); + + librados::IoCtx ioctx; + ASSERT_EQ(0, m_cluster->ioctx_create2(pool_id, ioctx)); + ioctx.application_enable("rbd", true); + + m_pool_watcher.reset(new PoolWatcher<>(m_threads, ioctx, + m_pool_watcher_listener)); + + if (enable_mirroring) { + ASSERT_EQ(0, librbd::api::Mirror<>::mode_set(ioctx, + RBD_MIRROR_MODE_POOL)); + std::string uuid; + ASSERT_EQ(0, librbd::api::Mirror<>::peer_add(ioctx, &uuid, + peer.cluster_name, + peer.client_name)); + } + if (name != nullptr) { + *name = pool_name; + } + + m_pool_watcher->init(); + } + + string get_image_id(librados::IoCtx *ioctx, const string &image_name) { + string obj = librbd::util::id_obj_name(image_name); + string id; + EXPECT_EQ(0, librbd::cls_client::get_id(ioctx, obj, &id)); + return id; + } + + void create_image(const string &pool_name, bool mirrored=true, + string *image_name=nullptr) { + uint64_t features = librbd::util::get_rbd_default_features(g_ceph_context); + string name = "image" + stringify(++m_image_number); + if (mirrored) { + features |= RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING; + } + + librados::IoCtx ioctx; + ASSERT_EQ(0, m_cluster->ioctx_create(pool_name.c_str(), ioctx)); + int order = 0; + ASSERT_EQ(0, librbd::create(ioctx, name.c_str(), 1 << 22, false, + features, &order, 0, 0)); + if (mirrored) { + librbd::Image image; + librbd::RBD rbd; + rbd.open(ioctx, image, name.c_str()); + image.mirror_image_enable(); + + librbd::mirror_image_info_t mirror_image_info; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image_info, + sizeof(mirror_image_info))); + image.close(); + + m_mirrored_images.insert(ImageId( + mirror_image_info.global_id, get_image_id(&ioctx, name))); + } + if (image_name != nullptr) + *image_name = name; + } + + void clone_image(const string &parent_pool_name, + const string &parent_image_name, + const string &clone_pool_name, + bool mirrored=true, + string *image_name=nullptr) { + librados::IoCtx pioctx, cioctx; + ASSERT_EQ(0, m_cluster->ioctx_create(parent_pool_name.c_str(), pioctx)); + ASSERT_EQ(0, m_cluster->ioctx_create(clone_pool_name.c_str(), cioctx)); + + string snap_name = "snap" + stringify(++m_snap_number); + { + librbd::ImageCtx *ictx = new librbd::ImageCtx(parent_image_name.c_str(), + "", "", pioctx, false); + ictx->state->open(0); + EXPECT_EQ(0, ictx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str())); + EXPECT_EQ(0, ictx->operations->snap_protect(cls::rbd::UserSnapshotNamespace(), + snap_name.c_str())); + ictx->state->close(); + } + + uint64_t features = librbd::util::get_rbd_default_features(g_ceph_context); + string name = "clone" + stringify(++m_image_number); + if (mirrored) { + features |= RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING; + } + int order = 0; + librbd::clone(pioctx, parent_image_name.c_str(), snap_name.c_str(), + cioctx, name.c_str(), features, &order, 0, 0); + if (mirrored) { + librbd::Image image; + librbd::RBD rbd; + rbd.open(cioctx, image, name.c_str()); + image.mirror_image_enable(); + + librbd::mirror_image_info_t mirror_image_info; + ASSERT_EQ(0, image.mirror_image_get_info(&mirror_image_info, + sizeof(mirror_image_info))); + image.close(); + + m_mirrored_images.insert(ImageId( + mirror_image_info.global_id, get_image_id(&cioctx, name))); + } + if (image_name != nullptr) + *image_name = name; + } + + void check_images() { + Mutex::Locker l(m_lock); + while (m_mirrored_images != m_pool_watcher_listener.image_ids) { + if (m_pool_watcher_listener.cond.WaitInterval( + m_lock, utime_t(10, 0)) != 0) { + break; + } + } + + ASSERT_EQ(m_mirrored_images, m_pool_watcher_listener.image_ids); + } + + Mutex m_lock; + RadosRef m_cluster; + PoolWatcherListener m_pool_watcher_listener; + unique_ptr > m_pool_watcher; + + set m_pools; + ImageIds m_mirrored_images; + + uint64_t m_image_number; + uint64_t m_snap_number; +}; + +TEST_F(TestPoolWatcher, EmptyPool) { + string uuid1 = "00000000-0000-0000-0000-000000000001"; + PeerSpec site1(uuid1, "site1", "mirror1"); + create_pool(true, site1); + check_images(); +} + +TEST_F(TestPoolWatcher, ReplicatedPools) { + string uuid1 = "00000000-0000-0000-0000-000000000001"; + PeerSpec site1(uuid1, "site1", "mirror1"); + string first_pool, local_pool, last_pool; + create_pool(true, site1, &first_pool); + check_images(); + create_image(first_pool); + check_images(); + string parent_image, parent_image2; + create_image(first_pool, true, &parent_image); + check_images(); + clone_image(first_pool, parent_image, first_pool); + check_images(); + clone_image(first_pool, parent_image, first_pool, true, &parent_image2); + check_images(); + create_image(first_pool, false); + check_images(); +} diff --git a/src/test/rbd_mirror/test_fixture.cc b/src/test/rbd_mirror/test_fixture.cc new file mode 100644 index 00000000..b0b345fb --- /dev/null +++ b/src/test/rbd_mirror/test_fixture.cc @@ -0,0 +1,160 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "cls/rbd/cls_rbd_types.h" +#include "test/rbd_mirror/test_fixture.h" +#include "include/stringify.h" +#include "include/rbd/librbd.hpp" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "test/librados/test_cxx.h" +#include "tools/rbd_mirror/Threads.h" + +namespace rbd { +namespace mirror { + +std::string TestFixture::_local_pool_name; +std::string TestFixture::_remote_pool_name; +std::shared_ptr TestFixture::_rados; +uint64_t TestFixture::_image_number = 0; +std::string TestFixture::_data_pool; + +TestFixture::TestFixture() { +} + +void TestFixture::SetUpTestCase() { + _rados = std::shared_ptr(new librados::Rados()); + ASSERT_EQ("", connect_cluster_pp(*_rados.get())); + ASSERT_EQ(0, _rados->conf_set("rbd_cache", "false")); + + _local_pool_name = get_temp_pool_name("test-rbd-mirror-"); + ASSERT_EQ(0, _rados->pool_create(_local_pool_name.c_str())); + + librados::IoCtx local_ioctx; + ASSERT_EQ(0, _rados->ioctx_create(_local_pool_name.c_str(), local_ioctx)); + local_ioctx.application_enable("rbd", true); + + _remote_pool_name = get_temp_pool_name("test-rbd-mirror-"); + ASSERT_EQ(0, _rados->pool_create(_remote_pool_name.c_str())); + + librados::IoCtx remote_ioctx; + ASSERT_EQ(0, _rados->ioctx_create(_remote_pool_name.c_str(), remote_ioctx)); + remote_ioctx.application_enable("rbd", true); + + ASSERT_EQ(0, create_image_data_pool(_data_pool)); + if (!_data_pool.empty()) { + printf("using image data pool: %s\n", _data_pool.c_str()); + } +} + +void TestFixture::TearDownTestCase() { + if (!_data_pool.empty()) { + ASSERT_EQ(0, _rados->pool_delete(_data_pool.c_str())); + } + + ASSERT_EQ(0, _rados->pool_delete(_remote_pool_name.c_str())); + ASSERT_EQ(0, _rados->pool_delete(_local_pool_name.c_str())); + _rados->shutdown(); +} + +void TestFixture::SetUp() { + static bool seeded = false; + if (!seeded) { + seeded = true; + int seed = getpid(); + cout << "seed " << seed << std::endl; + srand(seed); + } + + ASSERT_EQ(0, _rados->ioctx_create(_local_pool_name.c_str(), m_local_io_ctx)); + ASSERT_EQ(0, _rados->ioctx_create(_remote_pool_name.c_str(), m_remote_io_ctx)); + m_image_name = get_temp_image_name(); + + m_threads = new rbd::mirror::Threads<>(reinterpret_cast( + m_local_io_ctx.cct())); +} + +void TestFixture::TearDown() { + for (auto image_ctx : m_image_ctxs) { + image_ctx->state->close(); + } + + m_remote_io_ctx.close(); + m_local_io_ctx.close(); + + delete m_threads; +} + +int TestFixture::create_image(librbd::RBD &rbd, librados::IoCtx &ioctx, + const std::string &name, uint64_t size) { + int order = 18; + return rbd.create2(ioctx, name.c_str(), size, RBD_FEATURES_ALL, &order); +} + +int TestFixture::open_image(librados::IoCtx &io_ctx, + const std::string &image_name, + librbd::ImageCtx **image_ctx) { + *image_ctx = new librbd::ImageCtx(image_name.c_str(), "", nullptr, io_ctx, + false); + m_image_ctxs.insert(*image_ctx); + return (*image_ctx)->state->open(0); +} + +int TestFixture::create_snap(librbd::ImageCtx *image_ctx, const char* snap_name, + librados::snap_t *snap_id) { + int r = image_ctx->operations->snap_create(cls::rbd::UserSnapshotNamespace(), + snap_name); + if (r < 0) { + return r; + } + + r = image_ctx->state->refresh(); + if (r < 0) { + return r; + } + + if (image_ctx->snap_ids.count({cls::rbd::UserSnapshotNamespace(), + snap_name}) == 0) { + return -ENOENT; + } + + if (snap_id != nullptr) { + *snap_id = image_ctx->snap_ids[{cls::rbd::UserSnapshotNamespace(), + snap_name}]; + } + return 0; +} + +std::string TestFixture::get_temp_image_name() { + ++_image_number; + return "image" + stringify(_image_number); +} + +int TestFixture::create_image_data_pool(std::string &data_pool) { + std::string pool; + int r = _rados->conf_get("rbd_default_data_pool", pool); + if (r != 0) { + return r; + } else if (pool.empty()) { + return 0; + } + + r = _rados->pool_create(pool.c_str()); + if (r < 0) { + return r; + } + + librados::IoCtx data_ioctx; + r = _rados->ioctx_create(pool.c_str(), data_ioctx); + if (r < 0) { + return r; + } + + data_ioctx.application_enable("rbd", true); + data_pool = pool; + return 0; +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_fixture.h b/src/test/rbd_mirror/test_fixture.h new file mode 100644 index 00000000..217ae810 --- /dev/null +++ b/src/test/rbd_mirror/test_fixture.h @@ -0,0 +1,65 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_RBD_MIRROR_TEST_FIXTURE_H +#define CEPH_TEST_RBD_MIRROR_TEST_FIXTURE_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" +#include +#include +#include + +namespace librbd { +class ImageCtx; +class RBD; +} + +namespace rbd { +namespace mirror { + +template class Threads; + +class TestFixture : public ::testing::Test { +public: + TestFixture(); + + static void SetUpTestCase(); + static void TearDownTestCase(); + + void SetUp() override; + void TearDown() override; + + librados::IoCtx m_local_io_ctx; + librados::IoCtx m_remote_io_ctx; + + std::string m_image_name; + uint64_t m_image_size = 1 << 24; + + std::set m_image_ctxs; + + Threads *m_threads = nullptr; + + + int create_image(librbd::RBD &rbd, librados::IoCtx &ioctx, + const std::string &name, uint64_t size); + int open_image(librados::IoCtx &io_ctx, const std::string &image_name, + librbd::ImageCtx **image_ctx); + + int create_snap(librbd::ImageCtx *image_ctx, const char* snap_name, + librados::snap_t *snap_id = nullptr); + + static std::string get_temp_image_name(); + static int create_image_data_pool(std::string &data_pool); + + static std::string _local_pool_name; + static std::string _remote_pool_name; + static std::shared_ptr _rados; + static uint64_t _image_number; + static std::string _data_pool; +}; + +} // namespace mirror +} // namespace rbd + +#endif // CEPH_TEST_RBD_MIRROR_TEST_FIXTURE_H diff --git a/src/test/rbd_mirror/test_main.cc b/src/test/rbd_mirror/test_main.cc new file mode 100644 index 00000000..9226b086 --- /dev/null +++ b/src/test/rbd_mirror/test_main.cc @@ -0,0 +1,53 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "common/perf_counters.h" +#include "include/rados/librados.hpp" +#include "global/global_context.h" +#include "test/librados/test_cxx.h" +#include "gtest/gtest.h" +#include +#include + +PerfCounters *g_perf_counters = nullptr; + +extern void register_test_cluster_watcher(); +extern void register_test_image_policy(); +extern void register_test_image_sync(); +extern void register_test_instance_watcher(); +extern void register_test_instances(); +extern void register_test_leader_watcher(); +extern void register_test_pool_watcher(); +extern void register_test_rbd_mirror(); +extern void register_test_rbd_mirror_image_deleter(); + +int main(int argc, char **argv) +{ + register_test_cluster_watcher(); + register_test_image_policy(); + register_test_image_sync(); + register_test_instance_watcher(); + register_test_instances(); + register_test_leader_watcher(); + register_test_pool_watcher(); + register_test_rbd_mirror(); + register_test_rbd_mirror_image_deleter(); + + ::testing::InitGoogleTest(&argc, argv); + + librados::Rados rados; + std::string result = connect_cluster_pp(rados); + if (result != "" ) { + std::cerr << result << std::endl; + return 1; + } + + g_ceph_context = reinterpret_cast(rados.cct()); + + int r = rados.conf_set("lockdep", "true"); + if (r < 0) { + std::cerr << "failed to enable lockdep" << std::endl; + return -r; + } + return RUN_ALL_TESTS(); +} diff --git a/src/test/rbd_mirror/test_mock_ImageMap.cc b/src/test/rbd_mirror/test_mock_ImageMap.cc new file mode 100644 index 00000000..e223b8b9 --- /dev/null +++ b/src/test/rbd_mirror/test_mock_ImageMap.cc @@ -0,0 +1,1591 @@ +// -*- 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 "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/rbd_mirror/mock/MockContextWQ.h" +#include "test/rbd_mirror/mock/MockSafeTimer.h" +#include "librbd/MirroringWatcher.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/ImageMap.h" +#include "tools/rbd_mirror/image_map/LoadRequest.h" +#include "tools/rbd_mirror/image_map/UpdateRequest.h" +#include "tools/rbd_mirror/image_map/Types.h" +#include "include/stringify.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 { + +template <> +struct Threads { + MockSafeTimer *timer; + Mutex &timer_lock; + + MockContextWQ *work_queue; + + Threads(Threads *threads) + : timer(new MockSafeTimer()), + timer_lock(threads->timer_lock), + work_queue(new MockContextWQ()) { + } + ~Threads() { + delete timer; + delete work_queue; + } +}; + +namespace image_map { + +template <> +struct LoadRequest { + std::map *image_map; + Context *on_finish = nullptr; + + static LoadRequest *s_instance; + static LoadRequest *create(librados::IoCtx &ioctx, + std::map *image_map, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->image_map = image_map; + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + LoadRequest() { + s_instance = this; + } +}; + +template <> +struct UpdateRequest { + Context *on_finish = nullptr; + static UpdateRequest *s_instance; + static UpdateRequest *create(librados::IoCtx &ioctx, + std::map &&update_mapping, + std::set &&global_image_ids, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + UpdateRequest() { + s_instance = this; + } +}; + +LoadRequest * +LoadRequest::s_instance = nullptr; +UpdateRequest * +UpdateRequest::s_instance = nullptr; + +} // namespace image_map + +} // namespace mirror +} // namespace rbd + +// template definitions +#include "tools/rbd_mirror/ImageMap.cc" + +namespace rbd { +namespace mirror { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::WithArg; +using ::testing::AtLeast; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::ReturnArg; +using ::testing::StrEq; + +using image_map::Listener; +using image_map::LoadRequest; +using image_map::UpdateRequest; + +using ::rbd::mirror::Threads; + +class TestMockImageMap : public TestMockFixture { +public: + typedef Threads MockThreads; + typedef ImageMap MockImageMap; + typedef LoadRequest MockLoadRequest; + typedef UpdateRequest MockUpdateRequest; + + struct MockListener : Listener { + TestMockImageMap *test_mock_image_map; + + MockListener(TestMockImageMap *test_mock_image_map) + : test_mock_image_map(test_mock_image_map) { + } + + MOCK_METHOD2(mock_acquire_image, void(const std::string &, Context*)); + MOCK_METHOD2(mock_release_image, void(const std::string &, Context*)); + MOCK_METHOD3(mock_remove_image, void(const std::string &, + const std::string &, Context*)); + + void acquire_image(const std::string &global_image_id, + const std::string &instance_id, Context* on_finish) { + mock_acquire_image(global_image_id, on_finish); + } + + void release_image(const std::string &global_image_id, + const std::string &instance_id, Context* on_finish) { + mock_release_image(global_image_id, on_finish); + } + + void remove_image(const std::string &mirror_uuid, + const std::string &global_image_id, + const std::string &instance_id, Context* on_finish) { + mock_remove_image(mirror_uuid, global_image_id, on_finish); + } + }; + + TestMockImageMap() + : m_lock("TestMockImageMap::m_lock"), + m_notify_update_count(0), + m_map_update_count(0) { + } + + void SetUp() override { + TestFixture::SetUp(); + + m_local_instance_id = stringify(m_local_io_ctx.get_instance_id()); + + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_migration_throttle", + "0")); + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_type", "simple")); + } + + void TearDown() override { + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_type", "none")); + + TestFixture::TearDown(); + } + + void expect_work_queue(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(MockThreads &mock_threads) { + EXPECT_CALL(*mock_threads.timer, add_event_after(_,_)) + .WillOnce(DoAll(WithArg<1>(Invoke([this](Context *ctx) { + auto wrapped_ctx = new FunctionContext([this, ctx](int r) { + Mutex::Locker timer_locker(m_threads->timer_lock); + ctx->complete(r); + }); + m_threads->work_queue->queue(wrapped_ctx, 0); + })), ReturnArg<1>())); + } + + void expect_rebalance_event(MockThreads &mock_threads) { + EXPECT_CALL(*mock_threads.timer, add_event_after(_,_)) + .WillOnce(DoAll(WithArg<1>(Invoke([this](Context *ctx) { + // disable rebalance so as to not reschedule it again + CephContext *cct = reinterpret_cast(m_local_io_ctx.cct()); + cct->_conf.set_val("rbd_mirror_image_policy_rebalance_timeout", "0"); + + auto wrapped_ctx = new FunctionContext([this, ctx](int r) { + Mutex::Locker timer_locker(m_threads->timer_lock); + ctx->complete(r); + }); + m_threads->work_queue->queue(wrapped_ctx, 0); + })), ReturnArg<1>())); + } + + void expect_load_request(MockLoadRequest &request, int r) { + EXPECT_CALL(request, send()) + .WillOnce(Invoke([&request, r]() { + request.on_finish->complete(r); + })); + } + + void expect_update_request(MockUpdateRequest &request, int r) { + EXPECT_CALL(request, send()) + .WillOnce(Invoke([this, &request, r]() { + request.on_finish->complete(r); + if (r == 0) { + Mutex::Locker locker(m_lock); + ++m_map_update_count; + m_cond.Signal(); + } + })); + } + + void expect_listener_acquire_image(MockListener &mock_listener, + const std::string &global_image_id, + std::map *peer_ack_ctxs) { + EXPECT_CALL(mock_listener, mock_acquire_image(global_image_id, _)) + .WillOnce(WithArg<1>(Invoke([this, global_image_id, peer_ack_ctxs](Context* ctx) { + Mutex::Locker locker(m_lock); + peer_ack_ctxs->insert({global_image_id, ctx}); + ++m_notify_update_count; + m_cond.Signal(); + }))); + } + + void expect_listener_release_image(MockListener &mock_listener, + const std::string &global_image_id, + std::map *peer_ack_ctxs) { + EXPECT_CALL(mock_listener, mock_release_image(global_image_id, _)) + .WillOnce(WithArg<1>(Invoke([this, global_image_id, peer_ack_ctxs](Context* ctx) { + Mutex::Locker locker(m_lock); + peer_ack_ctxs->insert({global_image_id, ctx}); + ++m_notify_update_count; + m_cond.Signal(); + }))); + } + + void expect_listener_remove_image(MockListener &mock_listener, + const std::string &mirror_uuid, + const std::string &global_image_id, + std::map *peer_ack_ctxs) { + EXPECT_CALL(mock_listener, + mock_remove_image(mirror_uuid, global_image_id, _)) + .WillOnce(WithArg<2>(Invoke([this, global_image_id, peer_ack_ctxs](Context* ctx) { + Mutex::Locker locker(m_lock); + peer_ack_ctxs->insert({global_image_id, ctx}); + ++m_notify_update_count; + m_cond.Signal(); + }))); + } + + void expect_listener_images_unmapped(MockListener &mock_listener, size_t count, + std::set *global_image_ids, + std::map *peer_ack_ctxs) { + EXPECT_CALL(mock_listener, mock_release_image(_, _)) + .Times(count) + .WillRepeatedly(Invoke([this, global_image_ids, peer_ack_ctxs](std::string global_image_id, Context* ctx) { + Mutex::Locker locker(m_lock); + global_image_ids->emplace(global_image_id); + peer_ack_ctxs->insert({global_image_id, ctx}); + ++m_notify_update_count; + m_cond.Signal(); + })); + } + + void remote_peer_ack_nowait(MockImageMap *image_map, + const std::set &global_image_ids, + int ret, + std::map *peer_ack_ctxs) { + for (auto& global_image_id : global_image_ids) { + auto it = peer_ack_ctxs->find(global_image_id); + ASSERT_TRUE(it != peer_ack_ctxs->end()); + auto ack_ctx = it->second; + peer_ack_ctxs->erase(it); + ack_ctx->complete(ret); + wait_for_scheduled_task(); + } + } + + void remote_peer_ack_wait(MockImageMap *image_map, + const std::set &global_image_ids, + int ret, + std::map *peer_ack_ctxs) { + for (auto& global_image_id : global_image_ids) { + auto it = peer_ack_ctxs->find(global_image_id); + ASSERT_TRUE(it != peer_ack_ctxs->end()); + auto ack_ctx = it->second; + peer_ack_ctxs->erase(it); + ack_ctx->complete(ret); + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_map_update(1)); + } + } + + void remote_peer_ack_listener_wait(MockImageMap *image_map, + const std::set &global_image_ids, + int ret, + std::map *peer_ack_ctxs) { + for (auto& global_image_id : global_image_ids) { + auto it = peer_ack_ctxs->find(global_image_id); + ASSERT_TRUE(it != peer_ack_ctxs->end()); + auto ack_ctx = it->second; + peer_ack_ctxs->erase(it); + ack_ctx->complete(ret); + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(1)); + } + } + + void update_map_and_acquire(MockThreads &mock_threads, + MockUpdateRequest &mock_update_request, + MockListener &mock_listener, + const std::set &global_image_ids, + int ret, + std::map *peer_ack_ctxs) { + for (auto const &global_image_id : global_image_ids) { + expect_add_event(mock_threads); + expect_update_request(mock_update_request, ret); + expect_add_event(mock_threads); + expect_listener_acquire_image(mock_listener, global_image_id, + peer_ack_ctxs); + } + } + + void update_map_request(MockThreads &mock_threads, + MockUpdateRequest &mock_update_request, + const std::set &global_image_ids, int ret) { + for (uint32_t i = 0; i < global_image_ids.size(); ++i) { + expect_add_event(mock_threads); + expect_update_request(mock_update_request, ret); + } + } + + void wait_for_scheduled_task() { + m_threads->work_queue->drain(); + } + + bool wait_for_listener_notify(uint32_t count) { + Mutex::Locker locker(m_lock); + while (m_notify_update_count < count) { + if (m_cond.WaitInterval(m_lock, utime_t(10, 0)) != 0) { + break; + } + } + + if (m_notify_update_count < count) { + return false; + } + + m_notify_update_count -= count; + return true; + } + + bool wait_for_map_update(uint32_t count) { + Mutex::Locker locker(m_lock); + while (m_map_update_count < count) { + if (m_cond.WaitInterval(m_lock, utime_t(10, 0)) != 0) { + break; + } + } + + if (m_map_update_count < count) { + return false; + } + + m_map_update_count -= count; + return true; + } + + int when_shut_down(MockImageMap *image_map) { + C_SaferCond ctx; + image_map->shut_down(&ctx); + return ctx.wait(); + } + + void listener_acquire_images(MockListener &mock_listener, + const std::set &global_image_ids, + std::map *peer_ack_ctxs) { + for (auto const &global_image_id : global_image_ids) { + expect_listener_acquire_image(mock_listener, global_image_id, + peer_ack_ctxs); + } + } + + void listener_release_images(MockListener &mock_listener, + const std::set &global_image_ids, + std::map *peer_ack_ctxs) { + for (auto const &global_image_id : global_image_ids) { + expect_listener_release_image(mock_listener, global_image_id, + peer_ack_ctxs); + } + } + + void listener_remove_images(MockListener &mock_listener, + const std::string &mirror_uuid, + std::set &global_image_ids, + std::map *peer_ack_ctxs) { + for (auto const &global_image_id : global_image_ids) { + expect_listener_remove_image(mock_listener, mirror_uuid, global_image_id, + peer_ack_ctxs); + } + } + + Mutex m_lock; + Cond m_cond; + uint32_t m_notify_update_count; + uint32_t m_map_update_count; + std::string m_local_instance_id; +}; + +TEST_F(TestMockImageMap, SetLocalImages) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set global_image_ids{ + "global id 1", "global id 2" + }; + std::set global_image_ids_ack(global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, global_image_ids, &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("", std::move(global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, AddRemoveLocalImage) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set initial_global_image_ids{ + "global id 1", "global id 2" + }; + std::set initial_global_image_ids_ack(initial_global_image_ids); + + std::set remove_global_image_ids{ + "global id 1", "global id 2" + }; + std::set remove_global_image_ids_ack(remove_global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, initial_global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("", std::move(initial_global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, + &peer_ack_ctxs); + + // RELEASE+REMOVE_MAPPING + expect_add_event(mock_threads); + listener_release_images(mock_listener, remove_global_image_ids, + &peer_ack_ctxs); + update_map_request(mock_threads, mock_update_request, remove_global_image_ids, + 0); + + // remove images + mock_image_map->update_images("", {}, std::move(remove_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(remove_global_image_ids_ack.size())); + + remote_peer_ack_wait(mock_image_map.get(), remove_global_image_ids_ack, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, AddRemoveRemoteImage) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set initial_global_image_ids{ + "global id 1", "global id 2" + }; + std::set initial_global_image_ids_ack(initial_global_image_ids); + + std::set remove_global_image_ids{ + "global id 1", "global id 2" + }; + std::set remove_global_image_ids_ack(remove_global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, initial_global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("uuid1", std::move(initial_global_image_ids), + {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, + &peer_ack_ctxs); + + // RELEASE+REMOVE_MAPPING + std::map peer_remove_ack_ctxs; + listener_remove_images(mock_listener, "uuid1", remove_global_image_ids, + &peer_remove_ack_ctxs); + expect_add_event(mock_threads); + listener_release_images(mock_listener, remove_global_image_ids, + &peer_ack_ctxs); + update_map_request(mock_threads, mock_update_request, remove_global_image_ids, + 0); + + // remove images + mock_image_map->update_images("uuid1", {}, std::move(remove_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(remove_global_image_ids_ack.size() * 2)); + + remote_peer_ack_nowait(mock_image_map.get(), remove_global_image_ids_ack, 0, + &peer_remove_ack_ctxs); + remote_peer_ack_wait(mock_image_map.get(), remove_global_image_ids_ack, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, AddRemoveRemoteImageDuplicateNotification) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set initial_global_image_ids{ + "global id 1", "global id 2" + }; + std::set initial_global_image_ids_dup(initial_global_image_ids); + std::set initial_global_image_ids_ack(initial_global_image_ids); + + std::set remove_global_image_ids{ + "global id 1", "global id 2" + }; + std::set remove_global_image_ids_dup(remove_global_image_ids); + std::set remove_global_image_ids_ack(remove_global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, initial_global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("uuid1", std::move(initial_global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); + + // trigger duplicate "add" event + wait_for_scheduled_task(); + mock_image_map->update_images("uuid1", std::move(initial_global_image_ids_dup), {}); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, + &peer_ack_ctxs); + + // RELEASE+REMOVE_MAPPING + std::map peer_remove_ack_ctxs; + listener_remove_images(mock_listener, "uuid1", remove_global_image_ids, + &peer_remove_ack_ctxs); + expect_add_event(mock_threads); + listener_release_images(mock_listener, remove_global_image_ids, + &peer_ack_ctxs); + update_map_request(mock_threads, mock_update_request, remove_global_image_ids, 0); + + // remove images + mock_image_map->update_images("uuid1", {}, std::move(remove_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(remove_global_image_ids_ack.size() * 2)); + + remote_peer_ack_nowait(mock_image_map.get(), remove_global_image_ids_ack, 0, + &peer_remove_ack_ctxs); + remote_peer_ack_wait(mock_image_map.get(), remove_global_image_ids_ack, 0, + &peer_ack_ctxs); + + // trigger duplicate "remove" notification + mock_image_map->update_images("uuid1", {}, std::move(remove_global_image_ids_dup)); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, AcquireImageErrorRetry) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set initial_global_image_ids{ + "global id 1", "global id 2" + }; + std::set initial_global_image_ids_ack(initial_global_image_ids); + + // UPDATE_MAPPING failure + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, -EIO); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, initial_global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("uuid1", std::move(initial_global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(initial_global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), initial_global_image_ids_ack, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, RemoveRemoteAndLocalImage) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + // remote image set + std::set initial_remote_global_image_ids{ + "global id 1" + }; + std::set initial_remote_global_image_ids_ack(initial_remote_global_image_ids); + + // local image set + std::set initial_local_global_image_ids{ + "global id 1" + }; + + // remote/local images to remove + std::set remote_remove_global_image_ids{ + "global id 1" + }; + std::set remote_remove_global_image_ids_ack(remote_remove_global_image_ids); + + std::set local_remove_global_image_ids{ + "global id 1" + }; + std::set local_remove_global_image_ids_ack(local_remove_global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, initial_remote_global_image_ids, + &peer_ack_ctxs); + + // initial remote image list + mock_image_map->update_images("uuid1", std::move(initial_remote_global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(initial_remote_global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), + initial_remote_global_image_ids_ack, 0, + &peer_ack_ctxs); + + // set initial local image list -- this is a no-op from policy pov + mock_image_map->update_images("", std::move(initial_local_global_image_ids), {}); + + // remove remote images -- this should be a no-op from policy pov + // except the listener notification + std::map peer_ack_remove_ctxs; + listener_remove_images(mock_listener, "uuid1", remote_remove_global_image_ids, + &peer_ack_remove_ctxs); + + mock_image_map->update_images("uuid1", {}, std::move(remote_remove_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(remote_remove_global_image_ids_ack.size())); + + // RELEASE+REMOVE_MAPPING + expect_add_event(mock_threads); + listener_release_images(mock_listener, local_remove_global_image_ids, + &peer_ack_ctxs); + update_map_request(mock_threads, mock_update_request, local_remove_global_image_ids, 0); + + // remove local images + mock_image_map->update_images("", {}, std::move(local_remove_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(local_remove_global_image_ids_ack.size())); + + remote_peer_ack_nowait(mock_image_map.get(), local_remove_global_image_ids_ack, + 0, &peer_ack_remove_ctxs); + remote_peer_ack_wait(mock_image_map.get(), local_remove_global_image_ids_ack, + 0, &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, AddInstance) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set global_image_ids{ + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5" + }; + std::set global_image_ids_ack(global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + mock_image_map->update_instances_added({m_local_instance_id}); + + std::set shuffled_global_image_ids; + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 3, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_instances_added({"9876"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, RemoveInstance) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set global_image_ids{ + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5" + }; + std::set global_image_ids_ack(global_image_ids); + + expect_add_event(mock_threads); + + // UPDATE_MAPPING+ACQUIRE + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, global_image_ids, + &peer_ack_ctxs); + + // set initial image list + mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + + // remote peer ACKs image acquire request -- completing action + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + mock_image_map->update_instances_added({m_local_instance_id}); + + std::set shuffled_global_image_ids; + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 3, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_instances_added({"9876"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + shuffled_global_image_ids.clear(); + + // remove added instance + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_instances_removed({"9876"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, AddInstancePingPongImageTest) { + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_image_policy_migration_throttle", "600")); + + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + std::set global_image_ids{ + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5", + "global id 6", "global id 7", "global id 8", "global id 9", "global id 10", + "global id 11", "global id 12", "global id 13", "global id 14" + }; + + std::map image_mapping; + for (auto& global_image_id : global_image_ids) { + image_mapping[global_image_id] = {m_local_instance_id, {}, {}}; + } + + // ACQUIRE + MockLoadRequest mock_load_request; + EXPECT_CALL(mock_load_request, send()).WillOnce( + Invoke([&mock_load_request, &image_mapping]() { + *mock_load_request.image_map = image_mapping; + mock_load_request.on_finish->complete(0); + })); + + expect_add_event(mock_threads); + MockListener mock_listener(this); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, global_image_ids, + &peer_ack_ctxs); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + mock_image_map->update_instances_added({m_local_instance_id}); + + std::set global_image_ids_ack(global_image_ids); + + // remote peer ACKs image acquire request -- completing action + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + listener_acquire_images(mock_listener, global_image_ids, + &peer_ack_ctxs); + + // set initial image list + mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + + // remote peer ACKs image acquire request -- completing action + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + std::set shuffled_global_image_ids; + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 7, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_instances_added({"9876"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + std::set migrated_global_image_ids(shuffled_global_image_ids); + shuffled_global_image_ids.clear(); + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 3, &shuffled_global_image_ids, + &peer_ack_ctxs); + + // add another instance + mock_image_map->update_instances_added({"5432"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + + // shuffle set should be distinct + std::set reshuffled; + std::set_intersection(migrated_global_image_ids.begin(), migrated_global_image_ids.end(), + shuffled_global_image_ids.begin(), shuffled_global_image_ids.end(), + std::inserter(reshuffled, reshuffled.begin())); + ASSERT_TRUE(reshuffled.empty()); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, RemoveInstanceWithRemoveImage) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set global_image_ids{ + "global id 1", "global id 2", "global id 3", "remote id 4", + }; + std::set global_image_ids_ack(global_image_ids); + + std::set remove_global_image_ids{ + "global id 1" + }; + std::set remove_global_image_ids_ack(remove_global_image_ids); + + expect_add_event(mock_threads); + // UPDATE_MAPPING+ACQUIRE + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + mock_image_map->update_instances_added({m_local_instance_id}); + + std::set shuffled_global_image_ids; + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_instances_added({"9876"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + std::set shuffled_global_image_ids_ack(shuffled_global_image_ids); + + // RELEASE + + std::map peer_ack_remove_ctxs; + listener_remove_images(mock_listener, "uuid1", shuffled_global_image_ids, + &peer_ack_remove_ctxs); + expect_add_event(mock_threads); + listener_release_images(mock_listener, shuffled_global_image_ids, + &peer_ack_ctxs); + expect_add_event(mock_threads); + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + expect_update_request(mock_update_request, 0); + + mock_image_map->update_images("uuid1", {}, std::move(shuffled_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids_ack.size() * 2)); + + // instance failed -- update policy for instance removal + mock_image_map->update_instances_removed({"9876"}); + + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, + -ENOENT, &peer_ack_remove_ctxs); + remote_peer_ack_wait(mock_image_map.get(), shuffled_global_image_ids, + -EBLACKLISTED, &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, AddErrorAndRemoveImage) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + mock_image_map->update_instances_added({m_local_instance_id}); + + std::set global_image_ids{ + "global id 1", "global id 2", "global id 3", "remote id 4", + }; + std::set global_image_ids_ack(global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("uuid1", std::move(global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + std::set shuffled_global_image_ids; + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_instances_added({"9876"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + wait_for_scheduled_task(); + + mock_image_map->update_instances_removed({"9876"}); + + std::set released_global_image_ids; + std::map release_peer_ack_ctxs; + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 1, &released_global_image_ids, + &release_peer_ack_ctxs); + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 1, &released_global_image_ids, + &release_peer_ack_ctxs); + + // instance blacklisted -- ACQUIRE request fails + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, + -EBLACKLISTED, &peer_ack_ctxs); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + std::map remap_peer_ack_ctxs; + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &remap_peer_ack_ctxs); + + // instance blacklisted -- RELEASE request fails + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + -ENOENT, &release_peer_ack_ctxs); + wait_for_scheduled_task(); + + // new peer acks acquire request + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &remap_peer_ack_ctxs); + wait_for_scheduled_task(); + + std::set shuffled_global_image_ids_ack(shuffled_global_image_ids); + + // remove image + std::map peer_ack_remove_ctxs; + listener_remove_images(mock_listener, "uuid1", shuffled_global_image_ids, + &peer_ack_remove_ctxs); + expect_add_event(mock_threads); + listener_release_images(mock_listener, shuffled_global_image_ids, + &peer_ack_ctxs); + update_map_request(mock_threads, mock_update_request, shuffled_global_image_ids, 0); + + mock_image_map->update_images("uuid1", {}, std::move(shuffled_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids_ack.size() * 2)); + + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids_ack, 0, + &peer_ack_remove_ctxs); + remote_peer_ack_wait(mock_image_map.get(), shuffled_global_image_ids_ack, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, MirrorUUIDUpdated) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + // remote image set + std::set initial_remote_global_image_ids{ + "global id 1", "global id 2", "global id 3" + }; + std::set initial_remote_global_image_ids_ack(initial_remote_global_image_ids); + + // remote/local images to remove + std::set remote_removed_global_image_ids{ + "global id 1", "global id 2", "global id 3" + }; + std::set remote_removed_global_image_ids_ack(remote_removed_global_image_ids); + + std::set remote_added_global_image_ids{ + "global id 1", "global id 2", "global id 3" + }; + std::set remote_added_global_image_ids_ack(remote_added_global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, initial_remote_global_image_ids, + &peer_ack_ctxs); + + // initial remote image list + mock_image_map->update_images("uuid1", std::move(initial_remote_global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(initial_remote_global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), + initial_remote_global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + // RELEASE+REMOVE_MAPPING + std::map peer_remove_ack_ctxs; + listener_remove_images(mock_listener, "uuid1", remote_removed_global_image_ids, + &peer_remove_ack_ctxs); + expect_add_event(mock_threads); + listener_release_images(mock_listener, remote_removed_global_image_ids, + &peer_ack_ctxs); + update_map_request(mock_threads, mock_update_request, remote_removed_global_image_ids, 0); + + mock_image_map->update_images("uuid1", {}, std::move(remote_removed_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(remote_removed_global_image_ids_ack.size() * 2)); + + remote_peer_ack_nowait(mock_image_map.get(), + remote_removed_global_image_ids_ack, 0, + &peer_remove_ack_ctxs); + remote_peer_ack_wait(mock_image_map.get(), + remote_removed_global_image_ids_ack, 0, + &peer_ack_ctxs); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + listener_acquire_images(mock_listener, remote_added_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_images("uuid2", std::move(remote_added_global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(remote_added_global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), + remote_added_global_image_ids_ack, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +TEST_F(TestMockImageMap, RebalanceImageMap) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + + MockLoadRequest mock_load_request; + expect_load_request(mock_load_request, 0); + + MockListener mock_listener(this); + + std::unique_ptr mock_image_map{ + MockImageMap::create(m_local_io_ctx, &mock_threads, m_local_instance_id, + mock_listener)}; + + C_SaferCond cond; + mock_image_map->init(&cond); + ASSERT_EQ(0, cond.wait()); + + std::set global_image_ids{ + "global id 1", "global id 2", "global id 3", "global id 4", "global id 5", + "global id 6", "global id 7", "global id 8", "global id 9", "global id 10", + }; + std::set global_image_ids_ack(global_image_ids); + + // UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + MockUpdateRequest mock_update_request; + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + std::map peer_ack_ctxs; + listener_acquire_images(mock_listener, global_image_ids, + &peer_ack_ctxs); + + // initial image list + mock_image_map->update_images("", std::move(global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(global_image_ids_ack.size())); + + // remote peer ACKs image acquire request + remote_peer_ack_nowait(mock_image_map.get(), global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + mock_image_map->update_instances_added({m_local_instance_id}); + + std::set shuffled_global_image_ids; + + // RELEASE+UPDATE_MAPPING+ACQUIRE + expect_add_event(mock_threads); + expect_listener_images_unmapped(mock_listener, 5, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_instances_added({"9876"}); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + + // remove all shuffled images -- make way for rebalance + std::set shuffled_global_image_ids_ack(shuffled_global_image_ids); + + // RELEASE+REMOVE_MAPPING + expect_add_event(mock_threads); + listener_release_images(mock_listener, shuffled_global_image_ids, + &peer_ack_ctxs); + update_map_request(mock_threads, mock_update_request, shuffled_global_image_ids, + 0); + + mock_image_map->update_images("", {}, std::move(shuffled_global_image_ids)); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids_ack.size())); + + remote_peer_ack_wait(mock_image_map.get(), shuffled_global_image_ids_ack, 0, + &peer_ack_ctxs); + wait_for_scheduled_task(); + + shuffled_global_image_ids.clear(); + shuffled_global_image_ids_ack.clear(); + + std::set new_global_image_ids = { + "global id 11" + }; + std::set new_global_image_ids_ack(new_global_image_ids); + + expect_add_event(mock_threads); + expect_update_request(mock_update_request, 0); + expect_add_event(mock_threads); + listener_acquire_images(mock_listener, new_global_image_ids, &peer_ack_ctxs); + + expect_rebalance_event(mock_threads); // rebalance task + expect_add_event(mock_threads); // update task scheduled by + // rebalance task + expect_listener_images_unmapped(mock_listener, 2, &shuffled_global_image_ids, + &peer_ack_ctxs); + + mock_image_map->update_images("", std::move(new_global_image_ids), {}); + + ASSERT_TRUE(wait_for_map_update(1)); + ASSERT_TRUE(wait_for_listener_notify(new_global_image_ids_ack.size())); + + // set rebalance interval + CephContext *cct = reinterpret_cast(m_local_io_ctx.cct()); + cct->_conf.set_val("rbd_mirror_image_policy_rebalance_timeout", "5"); + remote_peer_ack_nowait(mock_image_map.get(), new_global_image_ids_ack, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_TRUE(wait_for_listener_notify(shuffled_global_image_ids.size())); + + update_map_and_acquire(mock_threads, mock_update_request, + mock_listener, shuffled_global_image_ids, 0, + &peer_ack_ctxs); + remote_peer_ack_listener_wait(mock_image_map.get(), shuffled_global_image_ids, + 0, &peer_ack_ctxs); + + // completion shuffle action for now (re)mapped images + remote_peer_ack_nowait(mock_image_map.get(), shuffled_global_image_ids, 0, + &peer_ack_ctxs); + + wait_for_scheduled_task(); + ASSERT_EQ(0, when_shut_down(mock_image_map.get())); +} + +} // namespace mirror +} // namespace rbd 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 00000000..1bd330fd --- /dev/null +++ b/src/test/rbd_mirror/test_mock_ImageReplayer.cc @@ -0,0 +1,1397 @@ +// -*- 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/Replay.h" +#include "librbd/journal/Types.h" +#include "tools/rbd_mirror/ImageDeleter.h" +#include "tools/rbd_mirror/ImageReplayer.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/CloseImageRequest.h" +#include "tools/rbd_mirror/image_replayer/EventPreprocessor.h" +#include "tools/rbd_mirror/image_replayer/PrepareLocalImageRequest.h" +#include "tools/rbd_mirror/image_replayer/PrepareRemoteImageRequest.h" +#include "test/rbd_mirror/test_mock_fixture.h" +#include "test/journal/mock/MockJournaler.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockJournal.h" +#include "test/rbd_mirror/mock/MockContextWQ.h" +#include "test/rbd_mirror/mock/MockSafeTimer.h" + +namespace librbd { + +namespace { + +struct MockTestJournal; + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } + MockTestJournal *journal = nullptr; +}; + +struct MockTestJournal : public MockJournal { + MOCK_METHOD2(start_external_replay, void(journal::Replay **, + Context *on_start)); + MOCK_METHOD0(stop_external_replay, void()); +}; + +} // anonymous namespace + +namespace journal { + +template<> +struct Replay { + 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*)); +}; + +template <> +struct TypeTraits { + typedef ::journal::MockJournalerProxy Journaler; + typedef ::journal::MockReplayEntryProxy ReplayEntry; +}; + +struct MirrorPeerClientMeta; + +} // namespace journal +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct ImageDeleter { + 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* ImageDeleter::s_instance = nullptr; + +template <> +struct Threads { + MockSafeTimer *timer; + Mutex &timer_lock; + + MockContextWQ *work_queue; + + Threads(Threads *threads) + : timer(new MockSafeTimer()), + timer_lock(threads->timer_lock), + work_queue(new MockContextWQ()) { + } + ~Threads() { + delete timer; + delete work_queue; + } +}; + +template<> +class InstanceWatcher { +}; + +namespace image_replayer { + +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; + +template<> +struct PrepareLocalImageRequest { + static PrepareLocalImageRequest* s_instance; + std::string *local_image_id = nullptr; + std::string *local_image_name = nullptr; + std::string *tag_owner = nullptr; + Context *on_finish = nullptr; + + static PrepareLocalImageRequest* create(librados::IoCtx &, + const std::string &global_image_id, + std::string *local_image_id, + std::string *local_image_name, + std::string *tag_owner, + MockContextWQ *work_queue, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->local_image_id = local_image_id; + s_instance->local_image_name = local_image_name; + s_instance->tag_owner = tag_owner; + s_instance->on_finish = on_finish; + return s_instance; + } + + PrepareLocalImageRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template<> +struct PrepareRemoteImageRequest { + static PrepareRemoteImageRequest* s_instance; + std::string *remote_mirror_uuid = nullptr; + std::string *remote_image_id = nullptr; + cls::journal::ClientState *client_state; + ::journal::MockJournalerProxy **remote_journaler = nullptr; + librbd::journal::MirrorPeerClientMeta *client_meta = nullptr; + Context *on_finish = nullptr; + + static PrepareRemoteImageRequest* create(Threads *threads, + librados::IoCtx &, + const std::string &global_image_id, + const std::string &local_mirror_uuid, + const std::string &local_image_id, + const journal::Settings &settings, + std::string *remote_mirror_uuid, + std::string *remote_image_id, + ::journal::MockJournalerProxy **remote_journaler, + cls::journal::ClientState *client_state, + librbd::journal::MirrorPeerClientMeta *client_meta, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->remote_mirror_uuid = remote_mirror_uuid; + s_instance->remote_image_id = remote_image_id; + s_instance->remote_journaler = remote_journaler; + s_instance->client_state = client_state; + s_instance->client_meta = client_meta; + s_instance->on_finish = on_finish; + return s_instance; + } + + PrepareRemoteImageRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template<> +struct BootstrapRequest { + static BootstrapRequest* s_instance; + librbd::MockTestImageCtx **image_ctx = nullptr; + Context *on_finish = nullptr; + bool *do_resync = nullptr; + + static BootstrapRequest* create( + Threads* threads, + librados::IoCtx &local_io_ctx, librados::IoCtx &remote_io_ctx, + rbd::mirror::InstanceWatcher *instance_watcher, + librbd::MockTestImageCtx **local_image_ctx, + const std::string &local_image_name, const std::string &remote_image_id, + const std::string &global_image_id, + const std::string &local_mirror_uuid, + const std::string &remote_mirror_uuid, + ::journal::MockJournalerProxy *journaler, + cls::journal::ClientState *client_state, + librbd::journal::MirrorPeerClientMeta *client_meta, + Context *on_finish, bool *do_resync, + rbd::mirror::ProgressContext *progress_ctx = nullptr) { + ceph_assert(s_instance != nullptr); + s_instance->image_ctx = local_image_ctx; + s_instance->on_finish = on_finish; + s_instance->do_resync = do_resync; + 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() { + } + + inline bool is_syncing() const { + return false; + } + + MOCK_METHOD0(send, void()); + MOCK_METHOD0(cancel, void()); +}; + +template<> +struct CloseImageRequest { + 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()); +}; + +template<> +struct EventPreprocessor { + static EventPreprocessor *s_instance; + + static EventPreprocessor *create(librbd::MockTestImageCtx &local_image_ctx, + ::journal::MockJournalerProxy &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 { + static ReplayStatusFormatter* s_instance; + + static ReplayStatusFormatter* create(::journal::MockJournalerProxy *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_METHOD2(get_or_send_update, bool(std::string *description, Context *on_finish)); +}; + +BootstrapRequest* BootstrapRequest::s_instance = nullptr; +CloseImageRequest* CloseImageRequest::s_instance = nullptr; +EventPreprocessor* EventPreprocessor::s_instance = nullptr; +PrepareLocalImageRequest* PrepareLocalImageRequest::s_instance = nullptr; +PrepareRemoteImageRequest* PrepareRemoteImageRequest::s_instance = nullptr; +ReplayStatusFormatter* ReplayStatusFormatter::s_instance = nullptr; + +} // namespace image_replayer +} // namespace mirror +} // namespace rbd + +// template definitions +#include "tools/rbd_mirror/ImageReplayer.cc" + +namespace rbd { +namespace mirror { + +class TestMockImageReplayer : public TestMockFixture { +public: + typedef Threads MockThreads; + typedef ImageDeleter MockImageDeleter; + typedef BootstrapRequest MockBootstrapRequest; + typedef CloseImageRequest MockCloseImageRequest; + typedef EventPreprocessor MockEventPreprocessor; + typedef PrepareLocalImageRequest MockPrepareLocalImageRequest; + typedef PrepareRemoteImageRequest MockPrepareRemoteImageRequest; + typedef ReplayStatusFormatter MockReplayStatusFormatter; + typedef librbd::journal::Replay MockReplay; + typedef ImageReplayer MockImageReplayer; + typedef InstanceWatcher 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_flush_repeatedly(MockReplay& mock_replay, + journal::MockJournaler& mock_journal) { + EXPECT_CALL(mock_replay, flush(_)) + .WillRepeatedly(Invoke([this](Context* ctx) { + m_threads->work_queue->queue(ctx, 0); + })); + EXPECT_CALL(mock_journal, flush_commit_position(_)) + .WillRepeatedly(Invoke([this](Context* ctx) { + m_threads->work_queue->queue(ctx, 0); + })); + } + + 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_get_or_send_update( + MockReplayStatusFormatter &mock_replay_status_formatter) { + EXPECT_CALL(mock_replay_status_formatter, get_or_send_update(_, _)) + .WillRepeatedly(DoAll(WithArg<1>(CompleteContext(-EEXIST)), + Return(true))); + } + + void expect_send(MockPrepareLocalImageRequest &mock_request, + const std::string &local_image_id, + const std::string &local_image_name, + const std::string &tag_owner, + int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([&mock_request, local_image_id, local_image_name, tag_owner, r]() { + if (r == 0) { + *mock_request.local_image_id = local_image_id; + *mock_request.local_image_name = local_image_name; + *mock_request.tag_owner = tag_owner; + } + mock_request.on_finish->complete(r); + })); + } + + void expect_send(MockPrepareRemoteImageRequest& mock_request, + const std::string& mirror_uuid, const std::string& image_id, + int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce(Invoke([&mock_request, image_id, mirror_uuid, r]() { + if (r >= 0) { + *mock_request.remote_journaler = new ::journal::MockJournalerProxy(); + } + + *mock_request.remote_mirror_uuid = mirror_uuid; + *mock_request.remote_image_id = image_id; + mock_request.on_finish->complete(r); + })); + } + + void expect_send(MockBootstrapRequest &mock_bootstrap_request, + librbd::MockTestImageCtx &mock_local_image_ctx, + bool do_resync, int r) { + EXPECT_CALL(mock_bootstrap_request, send()) + .WillOnce(Invoke([&mock_bootstrap_request, &mock_local_image_ctx, + do_resync, r]() { + if (r == 0) { + *mock_bootstrap_request.image_ctx = &mock_local_image_ctx; + *mock_bootstrap_request.do_resync = do_resync; + } + mock_bootstrap_request.on_finish->complete(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(r)))); + } + + void expect_init(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, init(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_get_cached_client(::journal::MockJournaler &mock_journaler, + int r) { + librbd::journal::ImageClientMeta image_client_meta; + image_client_meta.tag_class = 0; + + librbd::journal::ClientData client_data; + client_data.client_meta = image_client_meta; + + cls::journal::Client client; + encode(client_data, client.data); + + EXPECT_CALL(mock_journaler, get_cached_client("local_mirror_uuid", _)) + .WillOnce(DoAll(SetArgPointee<1>(client), + Return(r))); + } + + void expect_stop_replay(::journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, stop_replay(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_flush(MockReplay &mock_replay, int r) { + EXPECT_CALL(mock_replay, flush(_)).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(r))); + } + + void expect_shut_down(journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, shut_down(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_send(MockCloseImageRequest &mock_close_image_request, int r) { + EXPECT_CALL(mock_close_image_request, send()) + .WillOnce(Invoke([&mock_close_image_request, r]() { + *mock_close_image_request.image_ctx = nullptr; + mock_close_image_request.on_finish->complete(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(_))) + .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 create_image_replayer(MockThreads &mock_threads) { + m_image_replayer = new MockImageReplayer( + &mock_threads, &m_instance_watcher, + rbd::mirror::RadosRef(new librados::Rados(m_local_io_ctx)), + "local_mirror_uuid", m_local_io_ctx.get_id(), "global image id"); + m_image_replayer->add_peer("peer_uuid", m_remote_io_ctx); + } + + librbd::ImageCtx *m_remote_image_ctx; + librbd::ImageCtx *m_local_image_ctx = nullptr; + MockInstanceWatcher m_instance_watcher; + MockImageReplayer *m_image_replayer = nullptr; +}; + +TEST_F(TestMockImageReplayer, StartStop) { + // START + + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + librbd::MockTestJournal mock_local_journal; + mock_local_image_ctx.journal = &mock_local_journal; + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplay mock_local_replay; + MockEventPreprocessor mock_event_preprocessor; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_flush_repeatedly(mock_local_replay, mock_remote_journaler); + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, 0); + + EXPECT_CALL(mock_local_journal, add_listener(_)); + + expect_init(mock_remote_journaler, 0); + + EXPECT_CALL(mock_remote_journaler, add_listener(_)); + expect_get_cached_client(mock_remote_journaler, 0); + + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + + EXPECT_CALL(mock_remote_journaler, start_live_replay(_, _)); + + 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 + + MockCloseImageRequest mock_close_local_image_request; + + expect_shut_down(mock_local_replay, true, 0); + EXPECT_CALL(mock_local_journal, remove_listener(_)); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + expect_send(mock_close_local_image_request, 0); + + expect_stop_replay(mock_remote_journaler, 0); + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + 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); + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + "remote image id", 0); + EXPECT_CALL(mock_remote_journaler, construct()); + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(0, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, LocalImageDNE) { + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, "", "", "", -ENOENT); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, -EREMOTEIO); + + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(-EREMOTEIO, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, PrepareLocalImageError) { + 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; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", -EINVAL); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(-EINVAL, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, GetRemoteImageIdDNE) { + 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; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + "", -ENOENT); + expect_trash_move(mock_image_deleter, "global image id", false, 0); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(0, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, GetRemoteImageIdNonLinkedDNE) { + 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; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "some other mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + "", -ENOENT); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(-ENOENT, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, GetRemoteImageIdError) { + 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; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, -EINVAL); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(-EINVAL, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, BootstrapError) { + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, -EINVAL); + + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(-EINVAL, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, StopBeforeBootstrap) { + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()) + .WillOnce(Invoke([this]() { + m_image_replayer->stop(nullptr, true); + })); + + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(-ECANCELED, start_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, StartExternalReplayError) { + // START + + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + librbd::MockTestJournal mock_local_journal; + mock_local_image_ctx.journal = &mock_local_journal; + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplay mock_local_replay; + MockEventPreprocessor mock_event_preprocessor; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, 0); + + EXPECT_CALL(mock_local_journal, add_listener(_)); + + expect_init(mock_remote_journaler, 0); + + EXPECT_CALL(mock_remote_journaler, add_listener(_)); + expect_get_cached_client(mock_remote_journaler, 0); + + expect_start_external_replay(mock_local_journal, nullptr, -EINVAL); + + MockCloseImageRequest mock_close_local_image_request; + EXPECT_CALL(mock_local_journal, remove_listener(_)); + expect_send(mock_close_local_image_request, 0); + + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(-EINVAL, start_ctx.wait()); + ASSERT_EQ(image_replayer::HEALTH_STATE_ERROR, + m_image_replayer->get_health_state()); +} + +TEST_F(TestMockImageReplayer, StopError) { + // START + + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + librbd::MockTestJournal mock_local_journal; + mock_local_image_ctx.journal = &mock_local_journal; + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplay mock_local_replay; + MockEventPreprocessor mock_event_preprocessor; + MockReplayStatusFormatter mock_replay_status_formatter; + + expect_flush_repeatedly(mock_local_replay, mock_remote_journaler); + expect_get_or_send_update(mock_replay_status_formatter); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, 0); + + EXPECT_CALL(mock_local_journal, add_listener(_)); + + expect_init(mock_remote_journaler, 0); + + EXPECT_CALL(mock_remote_journaler, add_listener(_)); + expect_get_cached_client(mock_remote_journaler, 0); + + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + + EXPECT_CALL(mock_remote_journaler, start_live_replay(_, _)); + + 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) + + MockCloseImageRequest mock_close_local_image_request; + + expect_shut_down(mock_local_replay, true, -EINVAL); + EXPECT_CALL(mock_local_journal, remove_listener(_)); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + expect_send(mock_close_local_image_request, -EINVAL); + + expect_stop_replay(mock_remote_journaler, -EINVAL); + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, -EINVAL); + + C_SaferCond stop_ctx; + m_image_replayer->stop(&stop_ctx); + ASSERT_EQ(0, stop_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, Replay) { + // START + + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + librbd::MockTestJournal mock_local_journal; + mock_local_image_ctx.journal = &mock_local_journal; + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplay mock_local_replay; + MockEventPreprocessor mock_event_preprocessor; + MockReplayStatusFormatter mock_replay_status_formatter; + ::journal::MockReplayEntry mock_replay_entry; + + expect_flush_repeatedly(mock_local_replay, mock_remote_journaler); + expect_get_or_send_update(mock_replay_status_formatter); + 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; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, 0); + + EXPECT_CALL(mock_local_journal, add_listener(_)); + + expect_init(mock_remote_journaler, 0); + + EXPECT_CALL(mock_remote_journaler, add_listener(_)); + expect_get_cached_client(mock_remote_journaler, 0); + + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + + EXPECT_CALL(mock_remote_journaler, start_live_replay(_, _)); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(0, start_ctx.wait()); + + // REPLAY + + 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_replay, false, 0); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + 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_replay, decode(_, _)) + .WillOnce(Return(0)); + expect_preprocess(mock_event_preprocessor, false, 0); + expect_process(mock_local_replay, 0, 0); + + // the next event with preprocess + expect_try_pop_front(mock_remote_journaler, tag.tid, true); + EXPECT_CALL(mock_replay_entry, get_data()); + EXPECT_CALL(mock_local_replay, decode(_, _)) + .WillOnce(Return(0)); + expect_preprocess(mock_event_preprocessor, true, 0); + expect_process(mock_local_replay, 0, 0); + + // attempt to process the next event + C_SaferCond replay_ctx; + expect_try_pop_front_return_no_entries(mock_remote_journaler, &replay_ctx); + + // fire + m_image_replayer->handle_replay_ready(); + ASSERT_EQ(0, replay_ctx.wait()); + + // STOP + + MockCloseImageRequest mock_close_local_image_request; + expect_shut_down(mock_local_replay, true, 0); + EXPECT_CALL(mock_local_journal, remove_listener(_)); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + expect_send(mock_close_local_image_request, 0); + + expect_stop_replay(mock_remote_journaler, 0); + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + C_SaferCond stop_ctx; + m_image_replayer->stop(&stop_ctx); + ASSERT_EQ(0, stop_ctx.wait()); +} + +TEST_F(TestMockImageReplayer, DecodeError) { + // START + + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + librbd::MockTestJournal mock_local_journal; + mock_local_image_ctx.journal = &mock_local_journal; + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplay mock_local_replay; + MockEventPreprocessor mock_event_preprocessor; + MockReplayStatusFormatter mock_replay_status_formatter; + ::journal::MockReplayEntry mock_replay_entry; + + expect_flush_repeatedly(mock_local_replay, mock_remote_journaler); + expect_get_or_send_update(mock_replay_status_formatter); + expect_get_commit_tid_in_debug(mock_replay_entry); + expect_get_tag_tid_in_debug(mock_local_journal); + + InSequence seq; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, 0); + + EXPECT_CALL(mock_local_journal, add_listener(_)); + + expect_init(mock_remote_journaler, 0); + + EXPECT_CALL(mock_remote_journaler, add_listener(_)); + expect_get_cached_client(mock_remote_journaler, 0); + + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + + EXPECT_CALL(mock_remote_journaler, start_live_replay(_, _)); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(0, start_ctx.wait()); + + // REPLAY + + 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_replay, false, 0); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + 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_replay, decode(_, _)) + .WillOnce(Return(-EINVAL)); + + // stop on error + expect_shut_down(mock_local_replay, true, 0); + EXPECT_CALL(mock_local_journal, remove_listener(_)); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + + MockCloseImageRequest mock_close_local_image_request; + C_SaferCond close_ctx; + EXPECT_CALL(mock_close_local_image_request, send()) + .WillOnce(Invoke([&mock_close_local_image_request, &close_ctx]() { + *mock_close_local_image_request.image_ctx = nullptr; + mock_close_local_image_request.on_finish->complete(0); + close_ctx.complete(0); + })); + + expect_stop_replay(mock_remote_journaler, 0); + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + // fire + m_image_replayer->handle_replay_ready(); + ASSERT_EQ(0, close_ctx.wait()); + + while (!m_image_replayer->is_stopped()) { + usleep(1000); + } +} + +TEST_F(TestMockImageReplayer, DelayedReplay) { + + // START + + create_local_image(); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + + librbd::MockTestJournal mock_local_journal; + mock_local_image_ctx.journal = &mock_local_journal; + + journal::MockJournaler mock_remote_journaler; + MockThreads mock_threads(m_threads); + expect_work_queue_repeatedly(mock_threads); + expect_add_event_after_repeatedly(mock_threads); + + MockImageDeleter mock_image_deleter; + MockPrepareLocalImageRequest mock_prepare_local_image_request; + MockPrepareRemoteImageRequest mock_prepare_remote_image_request; + MockBootstrapRequest mock_bootstrap_request; + MockReplay mock_local_replay; + MockEventPreprocessor mock_event_preprocessor; + MockReplayStatusFormatter mock_replay_status_formatter; + ::journal::MockReplayEntry mock_replay_entry; + + expect_flush_repeatedly(mock_local_replay, mock_remote_journaler); + expect_get_or_send_update(mock_replay_status_formatter); + 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; + expect_send(mock_prepare_local_image_request, mock_local_image_ctx.id, + mock_local_image_ctx.name, "remote mirror uuid", 0); + expect_send(mock_prepare_remote_image_request, "remote mirror uuid", + m_remote_image_ctx->id, 0); + EXPECT_CALL(mock_remote_journaler, construct()); + expect_send(mock_bootstrap_request, mock_local_image_ctx, false, 0); + + EXPECT_CALL(mock_local_journal, add_listener(_)); + + expect_init(mock_remote_journaler, 0); + + EXPECT_CALL(mock_remote_journaler, add_listener(_)); + expect_get_cached_client(mock_remote_journaler, 0); + + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + + EXPECT_CALL(mock_remote_journaler, start_live_replay(_, _)); + + create_image_replayer(mock_threads); + + C_SaferCond start_ctx; + m_image_replayer->start(&start_ctx); + ASSERT_EQ(0, start_ctx.wait()); + + // REPLAY + + 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_replay, false, 0); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + expect_start_external_replay(mock_local_journal, &mock_local_replay, 0); + 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_replay, decode(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(event_entry), + Return(0))); + expect_preprocess(mock_event_preprocessor, false, 0); + expect_process(mock_local_replay, 0, 0); + + // 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 = 2; + m_image_replayer->handle_replay_ready(); + ASSERT_EQ(0, replay_ctx.wait()); + + // add a pending (delayed) entry before stop + expect_try_pop_front(mock_remote_journaler, tag.tid, true); + EXPECT_CALL(mock_replay_entry, get_data()); + C_SaferCond decode_ctx; + EXPECT_CALL(mock_local_replay, decode(_, _)) + .WillOnce(DoAll(Invoke([&decode_ctx](bufferlist::const_iterator* it, + librbd::journal::EventEntry *e) { + decode_ctx.complete(0); + }), + Return(0))); + + mock_local_image_ctx.mirroring_replay_delay = 10; + m_image_replayer->handle_replay_ready(); + ASSERT_EQ(0, decode_ctx.wait()); + + // STOP + + MockCloseImageRequest mock_close_local_image_request; + + expect_shut_down(mock_local_replay, true, 0); + EXPECT_CALL(mock_local_journal, remove_listener(_)); + EXPECT_CALL(mock_local_journal, stop_external_replay()); + expect_send(mock_close_local_image_request, 0); + + expect_stop_replay(mock_remote_journaler, 0); + EXPECT_CALL(mock_remote_journaler, remove_listener(_)); + expect_shut_down(mock_remote_journaler, 0); + + C_SaferCond stop_ctx; + m_image_replayer->stop(&stop_ctx); + ASSERT_EQ(0, stop_ctx.wait()); +} + + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_mock_ImageSync.cc b/src/test/rbd_mirror/test_mock_ImageSync.cc new file mode 100644 index 00000000..d4059140 --- /dev/null +++ b/src/test/rbd_mirror/test_mock_ImageSync.cc @@ -0,0 +1,430 @@ +// -*- 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/DeepCopyRequest.h" +#include "librbd/journal/Types.h" +#include "librbd/journal/TypeTraits.h" +#include "test/journal/mock/MockJournaler.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "tools/rbd_mirror/ImageSync.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.h" +#include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.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 { + typedef ::journal::MockJournaler Journaler; +}; + +} // namespace journal + +template <> +class DeepCopyRequest { +public: + static DeepCopyRequest* s_instance; + Context *on_finish; + + static DeepCopyRequest* create( + librbd::MockTestImageCtx *src_image_ctx, + librbd::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 librbd::deep_copy::ObjectNumber &object_number, + ContextWQ *work_queue, SnapSeqs *snap_seqs, ProgressContext *prog_ctx, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + DeepCopyRequest() { + s_instance = this; + } + + void put() { + } + + void get() { + } + + MOCK_METHOD0(cancel, void()); + MOCK_METHOD0(send, void()); +}; + +DeepCopyRequest* DeepCopyRequest::s_instance = nullptr; + +} // namespace librbd + +// template definitions +template class rbd::mirror::ImageSync; +#include "tools/rbd_mirror/ImageSync.cc" + +namespace rbd { +namespace mirror { + +template<> +struct InstanceWatcher { + MOCK_METHOD2(notify_sync_request, void(const std::string, Context *)); + MOCK_METHOD1(cancel_sync_request, bool(const std::string &)); + MOCK_METHOD1(notify_sync_complete, void(const std::string &)); +}; + +namespace image_sync { + +template <> +class SyncPointCreateRequest { +public: + static SyncPointCreateRequest *s_instance; + Context *on_finish; + + static SyncPointCreateRequest* create(librbd::MockTestImageCtx *remote_image_ctx, + const std::string &mirror_uuid, + journal::MockJournaler *journaler, + librbd::journal::MirrorPeerClientMeta *client_meta, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + SyncPointCreateRequest() { + s_instance = this; + } + MOCK_METHOD0(send, void()); +}; + +template <> +class SyncPointPruneRequest { +public: + static SyncPointPruneRequest *s_instance; + Context *on_finish; + bool sync_complete; + + static SyncPointPruneRequest* create(librbd::MockTestImageCtx *remote_image_ctx, + bool sync_complete, + journal::MockJournaler *journaler, + librbd::journal::MirrorPeerClientMeta *client_meta, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + s_instance->sync_complete = sync_complete; + return s_instance; + } + + SyncPointPruneRequest() { + s_instance = this; + } + MOCK_METHOD0(send, void()); +}; + +SyncPointCreateRequest* SyncPointCreateRequest::s_instance = nullptr; +SyncPointPruneRequest* SyncPointPruneRequest::s_instance = nullptr; + +} // namespace image_sync + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; +using ::testing::InvokeWithoutArgs; + +class TestMockImageSync : public TestMockFixture { +public: + typedef ImageSync MockImageSync; + typedef InstanceWatcher MockInstanceWatcher; + typedef image_sync::SyncPointCreateRequest MockSyncPointCreateRequest; + typedef image_sync::SyncPointPruneRequest MockSyncPointPruneRequest; + typedef librbd::DeepCopyRequest MockImageCopyRequest; + + 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_get_snap_id(librbd::MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_snap_id(_, _)) + .WillOnce(Return(123)); + } + + void expect_notify_sync_request(MockInstanceWatcher &mock_instance_watcher, + const std::string &sync_id, int r) { + EXPECT_CALL(mock_instance_watcher, notify_sync_request(sync_id, _)) + .WillOnce(Invoke([this, r](const std::string &, Context *on_sync_start) { + m_threads->work_queue->queue(on_sync_start, r); + })); + } + + void expect_cancel_sync_request(MockInstanceWatcher &mock_instance_watcher, + const std::string &sync_id, bool canceled) { + EXPECT_CALL(mock_instance_watcher, cancel_sync_request(sync_id)) + .WillOnce(Return(canceled)); + } + + void expect_notify_sync_complete(MockInstanceWatcher &mock_instance_watcher, + const std::string &sync_id) { + EXPECT_CALL(mock_instance_watcher, notify_sync_complete(sync_id)); + } + + void expect_create_sync_point(librbd::MockTestImageCtx &mock_local_image_ctx, + MockSyncPointCreateRequest &mock_sync_point_create_request, + int r) { + EXPECT_CALL(mock_sync_point_create_request, send()) + .WillOnce(Invoke([this, &mock_local_image_ctx, &mock_sync_point_create_request, r]() { + if (r == 0) { + mock_local_image_ctx.snap_ids[{cls::rbd::UserSnapshotNamespace(), + "snap1"}] = 123; + m_client_meta.sync_points.emplace_back(cls::rbd::UserSnapshotNamespace(), + "snap1", + boost::none); + } + m_threads->work_queue->queue(mock_sync_point_create_request.on_finish, r); + })); + } + + void expect_copy_image(MockImageCopyRequest &mock_image_copy_request, int r) { + EXPECT_CALL(mock_image_copy_request, send()) + .WillOnce(Invoke([this, &mock_image_copy_request, r]() { + m_threads->work_queue->queue(mock_image_copy_request.on_finish, r); + })); + } + + void expect_flush_sync_point(journal::MockJournaler &mock_journaler, int r) { + EXPECT_CALL(mock_journaler, update_client(_, _)) + .WillOnce(WithArg<1>(CompleteContext(r))); + } + + void expect_prune_sync_point(MockSyncPointPruneRequest &mock_sync_point_prune_request, + bool sync_complete, int r) { + EXPECT_CALL(mock_sync_point_prune_request, send()) + .WillOnce(Invoke([this, &mock_sync_point_prune_request, sync_complete, r]() { + ASSERT_EQ(sync_complete, mock_sync_point_prune_request.sync_complete); + if (r == 0 && !m_client_meta.sync_points.empty()) { + if (sync_complete) { + m_client_meta.sync_points.pop_front(); + } else { + while (m_client_meta.sync_points.size() > 1) { + m_client_meta.sync_points.pop_back(); + } + } + } + m_threads->work_queue->queue(mock_sync_point_prune_request.on_finish, r); + })); + } + + MockImageSync *create_request(librbd::MockTestImageCtx &mock_remote_image_ctx, + librbd::MockTestImageCtx &mock_local_image_ctx, + journal::MockJournaler &mock_journaler, + MockInstanceWatcher &mock_instance_watcher, + Context *ctx) { + return new MockImageSync(&mock_local_image_ctx, &mock_remote_image_ctx, + m_threads->timer, &m_threads->timer_lock, + "mirror-uuid", &mock_journaler, &m_client_meta, + m_threads->work_queue, &mock_instance_watcher, + ctx); + } + + librbd::ImageCtx *m_remote_image_ctx; + librbd::ImageCtx *m_local_image_ctx; + librbd::journal::MirrorPeerClientMeta m_client_meta; +}; + +TEST_F(TestMockImageSync, SimpleSync) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + journal::MockJournaler mock_journaler; + MockInstanceWatcher mock_instance_watcher; + MockImageCopyRequest mock_image_copy_request; + MockSyncPointCreateRequest mock_sync_point_create_request; + MockSyncPointPruneRequest mock_sync_point_prune_request; + + InSequence seq; + expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0); + expect_create_sync_point(mock_local_image_ctx, mock_sync_point_create_request, 0); + expect_get_snap_id(mock_remote_image_ctx); + expect_copy_image(mock_image_copy_request, 0); + expect_flush_sync_point(mock_journaler, 0); + expect_prune_sync_point(mock_sync_point_prune_request, true, 0); + expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id); + + C_SaferCond ctx; + MockImageSync *request = create_request(mock_remote_image_ctx, + mock_local_image_ctx, mock_journaler, + mock_instance_watcher, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageSync, RestartSync) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + journal::MockJournaler mock_journaler; + MockInstanceWatcher mock_instance_watcher; + MockImageCopyRequest mock_image_copy_request; + MockSyncPointCreateRequest mock_sync_point_create_request; + MockSyncPointPruneRequest mock_sync_point_prune_request; + + m_client_meta.sync_points = {{cls::rbd::UserSnapshotNamespace(), "snap1", boost::none}, + {cls::rbd::UserSnapshotNamespace(), "snap2", "snap1", boost::none}}; + mock_local_image_ctx.snap_ids[{cls::rbd::UserSnapshotNamespace(), "snap1"}] = 123; + mock_local_image_ctx.snap_ids[{cls::rbd::UserSnapshotNamespace(), "snap2"}] = 234; + + expect_test_features(mock_local_image_ctx); + + InSequence seq; + expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0); + expect_prune_sync_point(mock_sync_point_prune_request, false, 0); + expect_get_snap_id(mock_remote_image_ctx); + expect_copy_image(mock_image_copy_request, 0); + expect_flush_sync_point(mock_journaler, 0); + expect_prune_sync_point(mock_sync_point_prune_request, true, 0); + expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id); + + C_SaferCond ctx; + MockImageSync *request = create_request(mock_remote_image_ctx, + mock_local_image_ctx, mock_journaler, + mock_instance_watcher, &ctx); + request->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageSync, CancelNotifySyncRequest) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + journal::MockJournaler mock_journaler; + MockInstanceWatcher mock_instance_watcher; + + InSequence seq; + Context *on_sync_start = nullptr; + C_SaferCond notify_sync_ctx; + EXPECT_CALL(mock_instance_watcher, + notify_sync_request(mock_local_image_ctx.id, _)) + .WillOnce(Invoke([&on_sync_start, ¬ify_sync_ctx]( + const std::string &, Context *ctx) { + on_sync_start = ctx; + notify_sync_ctx.complete(0); + })); + EXPECT_CALL(mock_instance_watcher, + cancel_sync_request(mock_local_image_ctx.id)) + .WillOnce(Invoke([&on_sync_start](const std::string &) { + EXPECT_NE(nullptr, on_sync_start); + on_sync_start->complete(-ECANCELED); + return true; + })); + + C_SaferCond ctx; + MockImageSync *request = create_request(mock_remote_image_ctx, + mock_local_image_ctx, mock_journaler, + mock_instance_watcher, &ctx); + request->get(); + request->send(); + + // cancel the notify sync request once it starts + ASSERT_EQ(0, notify_sync_ctx.wait()); + request->cancel(); + request->put(); + + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockImageSync, CancelImageCopy) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + journal::MockJournaler mock_journaler; + MockInstanceWatcher mock_instance_watcher; + MockImageCopyRequest mock_image_copy_request; + MockSyncPointCreateRequest mock_sync_point_create_request; + MockSyncPointPruneRequest mock_sync_point_prune_request; + + m_client_meta.sync_points = {{cls::rbd::UserSnapshotNamespace(), "snap1", boost::none}}; + + InSequence seq; + expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0); + expect_prune_sync_point(mock_sync_point_prune_request, false, 0); + expect_get_snap_id(mock_remote_image_ctx); + + C_SaferCond image_copy_ctx; + EXPECT_CALL(mock_image_copy_request, send()) + .WillOnce(Invoke([&image_copy_ctx]() { + image_copy_ctx.complete(0); + })); + expect_cancel_sync_request(mock_instance_watcher, mock_local_image_ctx.id, + false); + EXPECT_CALL(mock_image_copy_request, cancel()); + expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id); + + C_SaferCond ctx; + MockImageSync *request = create_request(mock_remote_image_ctx, + mock_local_image_ctx, mock_journaler, + mock_instance_watcher, &ctx); + request->get(); + request->send(); + + // cancel the image copy once it starts + ASSERT_EQ(0, image_copy_ctx.wait()); + request->cancel(); + request->put(); + m_threads->work_queue->queue(mock_image_copy_request.on_finish, 0); + + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +TEST_F(TestMockImageSync, CancelAfterCopyImage) { + librbd::MockTestImageCtx mock_remote_image_ctx(*m_remote_image_ctx); + librbd::MockTestImageCtx mock_local_image_ctx(*m_local_image_ctx); + journal::MockJournaler mock_journaler; + MockInstanceWatcher mock_instance_watcher; + MockImageCopyRequest mock_image_copy_request; + MockSyncPointCreateRequest mock_sync_point_create_request; + MockSyncPointPruneRequest mock_sync_point_prune_request; + + C_SaferCond ctx; + MockImageSync *request = create_request(mock_remote_image_ctx, + mock_local_image_ctx, mock_journaler, + mock_instance_watcher, &ctx); + InSequence seq; + expect_notify_sync_request(mock_instance_watcher, mock_local_image_ctx.id, 0); + expect_create_sync_point(mock_local_image_ctx, mock_sync_point_create_request, 0); + expect_get_snap_id(mock_remote_image_ctx); + EXPECT_CALL(mock_image_copy_request, send()) + .WillOnce((DoAll(InvokeWithoutArgs([request]() { + request->cancel(); + }), + Invoke([this, &mock_image_copy_request]() { + m_threads->work_queue->queue(mock_image_copy_request.on_finish, 0); + })))); + expect_cancel_sync_request(mock_instance_watcher, mock_local_image_ctx.id, + false); + EXPECT_CALL(mock_image_copy_request, cancel()); + expect_notify_sync_complete(mock_instance_watcher, mock_local_image_ctx.id); + + request->send(); + ASSERT_EQ(-ECANCELED, ctx.wait()); +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_mock_ImageSyncThrottler.cc b/src/test/rbd_mirror/test_mock_ImageSyncThrottler.cc new file mode 100644 index 00000000..af88edcb --- /dev/null +++ b/src/test/rbd_mirror/test_mock_ImageSyncThrottler.cc @@ -0,0 +1,241 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "test/rbd_mirror/test_mock_fixture.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 librbd + +// template definitions +#include "tools/rbd_mirror/ImageSyncThrottler.cc" + +namespace rbd { +namespace mirror { + +class TestMockImageSyncThrottler : public TestMockFixture { +public: + typedef ImageSyncThrottler MockImageSyncThrottler; + +}; + +TEST_F(TestMockImageSyncThrottler, Single_Sync) { + MockImageSyncThrottler throttler(g_ceph_context); + C_SaferCond on_start; + throttler.start_op("id", &on_start); + ASSERT_EQ(0, on_start.wait()); + throttler.finish_op("id"); +} + +TEST_F(TestMockImageSyncThrottler, Multiple_Syncs) { + MockImageSyncThrottler throttler(g_ceph_context); + throttler.set_max_concurrent_syncs(2); + + C_SaferCond on_start1; + throttler.start_op("id1", &on_start1); + C_SaferCond on_start2; + throttler.start_op("id2", &on_start2); + C_SaferCond on_start3; + throttler.start_op("id3", &on_start3); + C_SaferCond on_start4; + throttler.start_op("id4", &on_start4); + + ASSERT_EQ(0, on_start2.wait()); + throttler.finish_op("id2"); + ASSERT_EQ(0, on_start3.wait()); + throttler.finish_op("id3"); + ASSERT_EQ(0, on_start1.wait()); + throttler.finish_op("id1"); + ASSERT_EQ(0, on_start4.wait()); + throttler.finish_op("id4"); +} + +TEST_F(TestMockImageSyncThrottler, Cancel_Running_Sync) { + MockImageSyncThrottler throttler(g_ceph_context); + C_SaferCond on_start; + throttler.start_op("id", &on_start); + ASSERT_EQ(0, on_start.wait()); + ASSERT_FALSE(throttler.cancel_op("id")); + throttler.finish_op("id"); +} + +TEST_F(TestMockImageSyncThrottler, Cancel_Waiting_Sync) { + MockImageSyncThrottler throttler(g_ceph_context); + throttler.set_max_concurrent_syncs(1); + + C_SaferCond on_start1; + throttler.start_op("id1", &on_start1); + C_SaferCond on_start2; + throttler.start_op("id2", &on_start2); + + ASSERT_EQ(0, on_start1.wait()); + ASSERT_TRUE(throttler.cancel_op("id2")); + ASSERT_EQ(-ECANCELED, on_start2.wait()); + throttler.finish_op("id1"); +} + + +TEST_F(TestMockImageSyncThrottler, Cancel_Running_Sync_Start_Waiting) { + MockImageSyncThrottler throttler(g_ceph_context); + throttler.set_max_concurrent_syncs(1); + + C_SaferCond on_start1; + throttler.start_op("id1", &on_start1); + C_SaferCond on_start2; + throttler.start_op("id2", &on_start2); + + ASSERT_EQ(0, on_start1.wait()); + ASSERT_FALSE(throttler.cancel_op("id1")); + throttler.finish_op("id1"); + ASSERT_EQ(0, on_start2.wait()); + throttler.finish_op("id2"); +} + +TEST_F(TestMockImageSyncThrottler, Duplicate) { + MockImageSyncThrottler throttler(g_ceph_context); + throttler.set_max_concurrent_syncs(1); + + C_SaferCond on_start1; + throttler.start_op("id1", &on_start1); + ASSERT_EQ(0, on_start1.wait()); + + C_SaferCond on_start2; + throttler.start_op("id1", &on_start2); + ASSERT_EQ(0, on_start2.wait()); + + C_SaferCond on_start3; + throttler.start_op("id2", &on_start3); + C_SaferCond on_start4; + throttler.start_op("id2", &on_start4); + ASSERT_EQ(-ENOENT, on_start3.wait()); + + throttler.finish_op("id1"); + ASSERT_EQ(0, on_start4.wait()); + throttler.finish_op("id2"); +} + +TEST_F(TestMockImageSyncThrottler, Duplicate2) { + MockImageSyncThrottler throttler(g_ceph_context); + throttler.set_max_concurrent_syncs(2); + + C_SaferCond on_start1; + throttler.start_op("id1", &on_start1); + ASSERT_EQ(0, on_start1.wait()); + C_SaferCond on_start2; + throttler.start_op("id2", &on_start2); + ASSERT_EQ(0, on_start2.wait()); + + C_SaferCond on_start3; + throttler.start_op("id3", &on_start3); + C_SaferCond on_start4; + throttler.start_op("id3", &on_start4); // dup + ASSERT_EQ(-ENOENT, on_start3.wait()); + + C_SaferCond on_start5; + throttler.start_op("id4", &on_start5); + + throttler.finish_op("id1"); + ASSERT_EQ(0, on_start4.wait()); + + throttler.finish_op("id2"); + ASSERT_EQ(0, on_start5.wait()); + + C_SaferCond on_start6; + throttler.start_op("id5", &on_start6); + + throttler.finish_op("id3"); + ASSERT_EQ(0, on_start6.wait()); + + throttler.finish_op("id4"); + throttler.finish_op("id5"); +} + +TEST_F(TestMockImageSyncThrottler, Increase_Max_Concurrent_Syncs) { + MockImageSyncThrottler throttler(g_ceph_context); + throttler.set_max_concurrent_syncs(2); + + C_SaferCond on_start1; + throttler.start_op("id1", &on_start1); + C_SaferCond on_start2; + throttler.start_op("id2", &on_start2); + C_SaferCond on_start3; + throttler.start_op("id3", &on_start3); + C_SaferCond on_start4; + throttler.start_op("id4", &on_start4); + C_SaferCond on_start5; + throttler.start_op("id5", &on_start5); + + ASSERT_EQ(0, on_start1.wait()); + ASSERT_EQ(0, on_start2.wait()); + + throttler.set_max_concurrent_syncs(4); + + ASSERT_EQ(0, on_start3.wait()); + ASSERT_EQ(0, on_start4.wait()); + + throttler.finish_op("id4"); + ASSERT_EQ(0, on_start5.wait()); + + throttler.finish_op("id1"); + throttler.finish_op("id2"); + throttler.finish_op("id3"); + throttler.finish_op("id5"); +} + +TEST_F(TestMockImageSyncThrottler, Decrease_Max_Concurrent_Syncs) { + MockImageSyncThrottler throttler(g_ceph_context); + throttler.set_max_concurrent_syncs(4); + + C_SaferCond on_start1; + throttler.start_op("id1", &on_start1); + C_SaferCond on_start2; + throttler.start_op("id2", &on_start2); + C_SaferCond on_start3; + throttler.start_op("id3", &on_start3); + C_SaferCond on_start4; + throttler.start_op("id4", &on_start4); + C_SaferCond on_start5; + throttler.start_op("id5", &on_start5); + + ASSERT_EQ(0, on_start1.wait()); + ASSERT_EQ(0, on_start2.wait()); + ASSERT_EQ(0, on_start3.wait()); + ASSERT_EQ(0, on_start4.wait()); + + throttler.set_max_concurrent_syncs(2); + + throttler.finish_op("id1"); + throttler.finish_op("id2"); + throttler.finish_op("id3"); + + ASSERT_EQ(0, on_start5.wait()); + + throttler.finish_op("id4"); + throttler.finish_op("id5"); +} + +} // namespace mirror +} // namespace rbd + diff --git a/src/test/rbd_mirror/test_mock_InstanceReplayer.cc b/src/test/rbd_mirror/test_mock_InstanceReplayer.cc new file mode 100644 index 00000000..79dce7ac --- /dev/null +++ b/src/test/rbd_mirror/test_mock_InstanceReplayer.cc @@ -0,0 +1,366 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/mock/MockImageCtx.h" +#include "test/rbd_mirror/test_mock_fixture.h" +#include "test/rbd_mirror/mock/MockContextWQ.h" +#include "test/rbd_mirror/mock/MockSafeTimer.h" +#include "tools/rbd_mirror/ImageReplayer.h" +#include "tools/rbd_mirror/InstanceWatcher.h" +#include "tools/rbd_mirror/InstanceReplayer.h" +#include "tools/rbd_mirror/ServiceDaemon.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/image_replayer/Types.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 Threads { + MockSafeTimer *timer; + Mutex &timer_lock; + Cond timer_cond; + + MockContextWQ *work_queue; + + Threads(Threads *threads) + : timer(new MockSafeTimer()), + timer_lock(threads->timer_lock), + work_queue(new MockContextWQ()) { + } + ~Threads() { + delete timer; + delete work_queue; + } +}; + +template<> +struct ServiceDaemon { + MOCK_METHOD3(add_or_update_attribute, + void(int64_t, const std::string&, + const service_daemon::AttributeValue&)); +}; + +template<> +struct InstanceWatcher { +}; + +template<> +struct ImageReplayer { + static ImageReplayer* s_instance; + std::string global_image_id; + + static ImageReplayer *create( + Threads *threads, + InstanceWatcher *instance_watcher, + RadosRef local, const std::string &local_mirror_uuid, int64_t local_pool_id, + const std::string &global_image_id) { + ceph_assert(s_instance != nullptr); + s_instance->global_image_id = global_image_id; + return s_instance; + } + + ImageReplayer() { + ceph_assert(s_instance == nullptr); + s_instance = this; + } + + virtual ~ImageReplayer() { + ceph_assert(s_instance == this); + s_instance = nullptr; + } + + MOCK_METHOD0(destroy, void()); + MOCK_METHOD2(start, void(Context *, bool)); + MOCK_METHOD2(stop, void(Context *, bool)); + MOCK_METHOD0(restart, void()); + MOCK_METHOD0(flush, void()); + MOCK_METHOD2(print_status, void(Formatter *, stringstream *)); + MOCK_METHOD2(add_peer, void(const std::string &, librados::IoCtx &)); + MOCK_METHOD0(get_global_image_id, const std::string &()); + MOCK_METHOD0(get_local_image_id, const std::string &()); + MOCK_METHOD0(is_running, bool()); + MOCK_METHOD0(is_stopped, bool()); + MOCK_METHOD0(is_blacklisted, bool()); + + MOCK_CONST_METHOD0(is_finished, bool()); + MOCK_METHOD1(set_finished, void(bool)); + + MOCK_CONST_METHOD0(get_health_state, image_replayer::HealthState()); +}; + +ImageReplayer* ImageReplayer::s_instance = nullptr; + +} // namespace mirror +} // namespace rbd + +// template definitions +#include "tools/rbd_mirror/InstanceReplayer.cc" + +namespace rbd { +namespace mirror { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnArg; +using ::testing::ReturnRef; +using ::testing::WithArg; + +class TestMockInstanceReplayer : public TestMockFixture { +public: + typedef Threads MockThreads; + typedef ImageReplayer MockImageReplayer; + typedef InstanceReplayer MockInstanceReplayer; + typedef InstanceWatcher MockInstanceWatcher; + typedef ServiceDaemon MockServiceDaemon; + + void expect_work_queue(MockThreads &mock_threads) { + EXPECT_CALL(*mock_threads.work_queue, queue(_, _)) + .WillOnce(Invoke([this](Context *ctx, int r) { + m_threads->work_queue->queue(ctx, r); + })); + } + + void expect_add_event_after(MockThreads &mock_threads, + Context** timer_ctx = nullptr) { + EXPECT_CALL(*mock_threads.timer, add_event_after(_, _)) + .WillOnce(DoAll( + WithArg<1>(Invoke([this, &mock_threads, timer_ctx](Context *ctx) { + ceph_assert(mock_threads.timer_lock.is_locked()); + if (timer_ctx != nullptr) { + *timer_ctx = ctx; + mock_threads.timer_cond.SignalOne(); + } else { + m_threads->work_queue->queue( + new FunctionContext([&mock_threads, ctx](int) { + Mutex::Locker timer_lock(mock_threads.timer_lock); + ctx->complete(0); + }), 0); + } + })), + ReturnArg<1>())); + } + + void expect_cancel_event(MockThreads &mock_threads, bool canceled) { + EXPECT_CALL(*mock_threads.timer, cancel_event(_)) + .WillOnce(Return(canceled)); + } +}; + +TEST_F(TestMockInstanceReplayer, AcquireReleaseImage) { + MockThreads mock_threads(m_threads); + MockServiceDaemon mock_service_daemon; + MockInstanceWatcher mock_instance_watcher; + MockImageReplayer mock_image_replayer; + MockInstanceReplayer instance_replayer( + &mock_threads, &mock_service_daemon, + rbd::mirror::RadosRef(new librados::Rados(m_local_io_ctx)), + "local_mirror_uuid", m_local_io_ctx.get_id()); + std::string global_image_id("global_image_id"); + + EXPECT_CALL(mock_image_replayer, get_global_image_id()) + .WillRepeatedly(ReturnRef(global_image_id)); + + InSequence seq; + expect_work_queue(mock_threads); + Context *timer_ctx = nullptr; + expect_add_event_after(mock_threads, &timer_ctx); + instance_replayer.init(); + instance_replayer.add_peer("peer_uuid", m_remote_io_ctx); + + // Acquire + + C_SaferCond on_acquire; + EXPECT_CALL(mock_image_replayer, add_peer("peer_uuid", _)); + EXPECT_CALL(mock_image_replayer, is_stopped()).WillOnce(Return(true)); + EXPECT_CALL(mock_image_replayer, is_blacklisted()).WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, is_finished()).WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, start(nullptr, false)); + expect_work_queue(mock_threads); + + instance_replayer.acquire_image(&mock_instance_watcher, global_image_id, + &on_acquire); + ASSERT_EQ(0, on_acquire.wait()); + + // Release + + C_SaferCond on_release; + + EXPECT_CALL(mock_image_replayer, is_stopped()) + .WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, is_running()) + .WillOnce(Return(false)); + expect_work_queue(mock_threads); + expect_add_event_after(mock_threads); + expect_work_queue(mock_threads); + EXPECT_CALL(mock_image_replayer, is_stopped()) + .WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, is_running()) + .WillOnce(Return(true)); + EXPECT_CALL(mock_image_replayer, stop(_, false)) + .WillOnce(CompleteContext(0)); + expect_work_queue(mock_threads); + EXPECT_CALL(mock_image_replayer, is_stopped()) + .WillOnce(Return(true)); + expect_work_queue(mock_threads); + EXPECT_CALL(mock_image_replayer, destroy()); + + instance_replayer.release_image("global_image_id", &on_release); + ASSERT_EQ(0, on_release.wait()); + + expect_work_queue(mock_threads); + expect_cancel_event(mock_threads, true); + expect_work_queue(mock_threads); + instance_replayer.shut_down(); + ASSERT_TRUE(timer_ctx != nullptr); + delete timer_ctx; +} + +TEST_F(TestMockInstanceReplayer, RemoveFinishedImage) { + MockThreads mock_threads(m_threads); + MockServiceDaemon mock_service_daemon; + MockInstanceWatcher mock_instance_watcher; + MockImageReplayer mock_image_replayer; + MockInstanceReplayer instance_replayer( + &mock_threads, &mock_service_daemon, + rbd::mirror::RadosRef(new librados::Rados(m_local_io_ctx)), + "local_mirror_uuid", m_local_io_ctx.get_id()); + std::string global_image_id("global_image_id"); + + EXPECT_CALL(mock_image_replayer, get_global_image_id()) + .WillRepeatedly(ReturnRef(global_image_id)); + + InSequence seq; + expect_work_queue(mock_threads); + Context *timer_ctx1 = nullptr; + expect_add_event_after(mock_threads, &timer_ctx1); + instance_replayer.init(); + instance_replayer.add_peer("peer_uuid", m_remote_io_ctx); + + // Acquire + + C_SaferCond on_acquire; + EXPECT_CALL(mock_image_replayer, add_peer("peer_uuid", _)); + EXPECT_CALL(mock_image_replayer, is_stopped()).WillOnce(Return(true)); + EXPECT_CALL(mock_image_replayer, is_blacklisted()).WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, is_finished()).WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, start(nullptr, false)); + expect_work_queue(mock_threads); + + instance_replayer.acquire_image(&mock_instance_watcher, global_image_id, + &on_acquire); + ASSERT_EQ(0, on_acquire.wait()); + + // periodic start timer + Context *timer_ctx2 = nullptr; + expect_add_event_after(mock_threads, &timer_ctx2); + + Context *start_image_replayers_ctx = nullptr; + EXPECT_CALL(*mock_threads.work_queue, queue(_, 0)) + .WillOnce(Invoke([&start_image_replayers_ctx](Context *ctx, int r) { + start_image_replayers_ctx = ctx; + })); + + ASSERT_TRUE(timer_ctx1 != nullptr); + { + Mutex::Locker timer_locker(mock_threads.timer_lock); + timer_ctx1->complete(0); + } + + // remove finished image replayer + EXPECT_CALL(mock_image_replayer, get_health_state()).WillOnce( + Return(image_replayer::HEALTH_STATE_OK)); + EXPECT_CALL(mock_image_replayer, is_stopped()).WillOnce(Return(true)); + EXPECT_CALL(mock_image_replayer, is_blacklisted()).WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, is_finished()).WillOnce(Return(true)); + EXPECT_CALL(mock_image_replayer, destroy()); + EXPECT_CALL(mock_service_daemon,add_or_update_attribute(_, _, _)).Times(3); + + ASSERT_TRUE(start_image_replayers_ctx != nullptr); + start_image_replayers_ctx->complete(0); + + // shut down + expect_work_queue(mock_threads); + expect_cancel_event(mock_threads, true); + expect_work_queue(mock_threads); + instance_replayer.shut_down(); + ASSERT_TRUE(timer_ctx2 != nullptr); + delete timer_ctx2; +} + +TEST_F(TestMockInstanceReplayer, Reacquire) { + MockThreads mock_threads(m_threads); + MockServiceDaemon mock_service_daemon; + MockInstanceWatcher mock_instance_watcher; + MockImageReplayer mock_image_replayer; + MockInstanceReplayer instance_replayer( + &mock_threads, &mock_service_daemon, + rbd::mirror::RadosRef(new librados::Rados(m_local_io_ctx)), + "local_mirror_uuid", m_local_io_ctx.get_id()); + std::string global_image_id("global_image_id"); + + EXPECT_CALL(mock_image_replayer, get_global_image_id()) + .WillRepeatedly(ReturnRef(global_image_id)); + + InSequence seq; + expect_work_queue(mock_threads); + Context *timer_ctx = nullptr; + expect_add_event_after(mock_threads, &timer_ctx); + instance_replayer.init(); + instance_replayer.add_peer("peer_uuid", m_remote_io_ctx); + + // Acquire + + EXPECT_CALL(mock_image_replayer, add_peer("peer_uuid", _)); + EXPECT_CALL(mock_image_replayer, is_stopped()).WillOnce(Return(true)); + EXPECT_CALL(mock_image_replayer, is_blacklisted()).WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, is_finished()).WillOnce(Return(false)); + EXPECT_CALL(mock_image_replayer, start(nullptr, false)); + expect_work_queue(mock_threads); + + C_SaferCond on_acquire1; + instance_replayer.acquire_image(&mock_instance_watcher, global_image_id, + &on_acquire1); + ASSERT_EQ(0, on_acquire1.wait()); + + // Re-acquire + EXPECT_CALL(mock_image_replayer, set_finished(false)); + EXPECT_CALL(mock_image_replayer, restart()); + expect_work_queue(mock_threads); + + C_SaferCond on_acquire2; + instance_replayer.acquire_image(&mock_instance_watcher, global_image_id, + &on_acquire2); + ASSERT_EQ(0, on_acquire2.wait()); + + expect_work_queue(mock_threads); + expect_cancel_event(mock_threads, true); + EXPECT_CALL(mock_image_replayer, is_stopped()).WillOnce(Return(true)); + expect_work_queue(mock_threads); + expect_work_queue(mock_threads); + EXPECT_CALL(mock_image_replayer, is_stopped()).WillOnce(Return(true)); + EXPECT_CALL(mock_image_replayer, destroy()); + instance_replayer.shut_down(); + ASSERT_TRUE(timer_ctx != nullptr); + delete timer_ctx; +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_mock_InstanceWatcher.cc b/src/test/rbd_mirror/test_mock_InstanceWatcher.cc new file mode 100644 index 00000000..075134d9 --- /dev/null +++ b/src/test/rbd_mirror/test_mock_InstanceWatcher.cc @@ -0,0 +1,988 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librados/AioCompletionImpl.h" +#include "librbd/ManagedLock.h" +#include "test/librados/test_cxx.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/rbd_mirror/test_mock_fixture.h" +#include "tools/rbd_mirror/InstanceReplayer.h" +#include "tools/rbd_mirror/ImageSyncThrottler.h" +#include "tools/rbd_mirror/InstanceWatcher.h" +#include "tools/rbd_mirror/Threads.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template <> +struct ManagedLock { + static ManagedLock* s_instance; + + static ManagedLock *create(librados::IoCtx& ioctx, ContextWQ *work_queue, + const std::string& oid, librbd::Watcher *watcher, + managed_lock::Mode mode, + bool blacklist_on_break_lock, + uint32_t blacklist_expire_seconds) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + ManagedLock() { + ceph_assert(s_instance == nullptr); + s_instance = this; + } + + ~ManagedLock() { + ceph_assert(s_instance == this); + s_instance = nullptr; + } + + MOCK_METHOD0(destroy, void()); + MOCK_METHOD1(shut_down, void(Context *)); + MOCK_METHOD1(acquire_lock, void(Context *)); + MOCK_METHOD2(get_locker, void(managed_lock::Locker *, Context *)); + MOCK_METHOD3(break_lock, void(const managed_lock::Locker &, bool, Context *)); +}; + +ManagedLock *ManagedLock::s_instance = nullptr; + +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct Threads { + Mutex &timer_lock; + SafeTimer *timer; + ContextWQ *work_queue; + + Threads(Threads *threads) + : timer_lock(threads->timer_lock), timer(threads->timer), + work_queue(threads->work_queue) { + } +}; + +template <> +struct InstanceReplayer { + MOCK_METHOD3(acquire_image, void(InstanceWatcher *, + const std::string &, Context *)); + MOCK_METHOD2(release_image, void(const std::string &, Context *)); + MOCK_METHOD3(remove_peer_image, void(const std::string&, const std::string&, + Context *)); +}; + +template <> +struct ImageSyncThrottler { + static ImageSyncThrottler* s_instance; + + static ImageSyncThrottler *create(CephContext *cct) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + ImageSyncThrottler() { + ceph_assert(s_instance == nullptr); + s_instance = this; + } + + virtual ~ImageSyncThrottler() { + ceph_assert(s_instance == this); + s_instance = nullptr; + } + + MOCK_METHOD0(destroy, void()); + MOCK_METHOD1(drain, void(int)); + MOCK_METHOD2(start_op, void(const std::string &, Context *)); + MOCK_METHOD1(finish_op, void(const std::string &)); +}; + +ImageSyncThrottler* ImageSyncThrottler::s_instance = nullptr; + +} // namespace mirror +} // namespace rbd + +// template definitions +#include "tools/rbd_mirror/InstanceWatcher.cc" + +namespace rbd { +namespace mirror { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockInstanceWatcher : public TestMockFixture { +public: + typedef librbd::ManagedLock MockManagedLock; + typedef InstanceReplayer MockInstanceReplayer; + typedef InstanceWatcher MockInstanceWatcher; + typedef Threads MockThreads; + + std::string m_instance_id; + std::string m_oid; + MockThreads *m_mock_threads; + + void SetUp() override { + TestFixture::SetUp(); + m_local_io_ctx.remove(RBD_MIRROR_LEADER); + EXPECT_EQ(0, m_local_io_ctx.create(RBD_MIRROR_LEADER, true)); + + m_instance_id = stringify(m_local_io_ctx.get_instance_id()); + m_oid = RBD_MIRROR_INSTANCE_PREFIX + m_instance_id; + + m_mock_threads = new MockThreads(m_threads); + } + + void TearDown() override { + delete m_mock_threads; + TestMockFixture::TearDown(); + } + + void expect_register_watch(librados::MockTestMemIoCtxImpl &mock_io_ctx) { + EXPECT_CALL(mock_io_ctx, aio_watch(m_oid, _, _, _)); + } + + void expect_register_watch(librados::MockTestMemIoCtxImpl &mock_io_ctx, + const std::string &instance_id) { + std::string oid = RBD_MIRROR_INSTANCE_PREFIX + instance_id; + EXPECT_CALL(mock_io_ctx, aio_watch(oid, _, _, _)); + } + + void expect_unregister_watch(librados::MockTestMemIoCtxImpl &mock_io_ctx) { + EXPECT_CALL(mock_io_ctx, aio_unwatch(_, _)); + } + + void expect_register_instance(librados::MockTestMemIoCtxImpl &mock_io_ctx, + int r) { + EXPECT_CALL(mock_io_ctx, exec(RBD_MIRROR_LEADER, _, StrEq("rbd"), + StrEq("mirror_instances_add"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_unregister_instance(librados::MockTestMemIoCtxImpl &mock_io_ctx, + int r) { + EXPECT_CALL(mock_io_ctx, exec(RBD_MIRROR_LEADER, _, StrEq("rbd"), + StrEq("mirror_instances_remove"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_acquire_lock(MockManagedLock &mock_managed_lock, int r) { + EXPECT_CALL(mock_managed_lock, acquire_lock(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_release_lock(MockManagedLock &mock_managed_lock, int r) { + EXPECT_CALL(mock_managed_lock, shut_down(_)).WillOnce(CompleteContext(r)); + } + + void expect_destroy_lock(MockManagedLock &mock_managed_lock, + Context *ctx = nullptr) { + EXPECT_CALL(mock_managed_lock, destroy()) + .WillOnce(Invoke([ctx]() { + if (ctx != nullptr) { + ctx->complete(0); + } + })); + } + + void expect_get_locker(MockManagedLock &mock_managed_lock, + const librbd::managed_lock::Locker &locker, int r) { + EXPECT_CALL(mock_managed_lock, get_locker(_, _)) + .WillOnce(Invoke([r, locker](librbd::managed_lock::Locker *out, + Context *ctx) { + if (r == 0) { + *out = locker; + } + ctx->complete(r); + })); + } + + void expect_break_lock(MockManagedLock &mock_managed_lock, + const librbd::managed_lock::Locker &locker, int r) { + EXPECT_CALL(mock_managed_lock, break_lock(locker, true, _)) + .WillOnce(WithArg<2>(CompleteContext(r))); + } +}; + +TEST_F(TestMockInstanceWatcher, InitShutdown) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + auto instance_watcher = new MockInstanceWatcher( + m_local_io_ctx, m_mock_threads->work_queue, nullptr, m_instance_id); + InSequence seq; + + // Init + expect_register_instance(mock_io_ctx, 0); + expect_register_watch(mock_io_ctx); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher->init()); + + // Shutdown + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx); + expect_unregister_instance(mock_io_ctx, 0); + instance_watcher->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher; +} + +TEST_F(TestMockInstanceWatcher, InitError) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + auto instance_watcher = new MockInstanceWatcher( + m_local_io_ctx, m_mock_threads->work_queue, nullptr, m_instance_id); + InSequence seq; + + expect_register_instance(mock_io_ctx, 0); + expect_register_watch(mock_io_ctx); + expect_acquire_lock(mock_managed_lock, -EINVAL); + expect_unregister_watch(mock_io_ctx); + expect_unregister_instance(mock_io_ctx, 0); + + ASSERT_EQ(-EINVAL, instance_watcher->init()); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher; +} + +TEST_F(TestMockInstanceWatcher, ShutdownError) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + auto instance_watcher = new MockInstanceWatcher( + m_local_io_ctx, m_mock_threads->work_queue, nullptr, m_instance_id); + InSequence seq; + + // Init + expect_register_instance(mock_io_ctx, 0); + expect_register_watch(mock_io_ctx); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher->init()); + + // Shutdown + expect_release_lock(mock_managed_lock, -EINVAL); + expect_unregister_watch(mock_io_ctx); + expect_unregister_instance(mock_io_ctx, 0); + instance_watcher->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher; +} + + +TEST_F(TestMockInstanceWatcher, Remove) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + librbd::managed_lock::Locker + locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + + InSequence seq; + + expect_get_locker(mock_managed_lock, locker, 0); + expect_break_lock(mock_managed_lock, locker, 0); + expect_unregister_instance(mock_io_ctx, 0); + C_SaferCond on_destroy; + expect_destroy_lock(mock_managed_lock, &on_destroy); + + C_SaferCond on_remove; + MockInstanceWatcher::remove_instance(m_local_io_ctx, + m_mock_threads->work_queue, + "instance_id", &on_remove); + ASSERT_EQ(0, on_remove.wait()); + ASSERT_EQ(0, on_destroy.wait()); +} + +TEST_F(TestMockInstanceWatcher, RemoveNoent) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + InSequence seq; + + expect_get_locker(mock_managed_lock, librbd::managed_lock::Locker(), -ENOENT); + expect_unregister_instance(mock_io_ctx, 0); + C_SaferCond on_destroy; + expect_destroy_lock(mock_managed_lock, &on_destroy); + + C_SaferCond on_remove; + MockInstanceWatcher::remove_instance(m_local_io_ctx, + m_mock_threads->work_queue, + "instance_id", &on_remove); + ASSERT_EQ(0, on_remove.wait()); + ASSERT_EQ(0, on_destroy.wait()); +} + +TEST_F(TestMockInstanceWatcher, ImageAcquireRelease) { + MockManagedLock mock_managed_lock; + + librados::IoCtx& io_ctx1 = m_local_io_ctx; + std::string instance_id1 = m_instance_id; + librados::MockTestMemIoCtxImpl &mock_io_ctx1(get_mock_io_ctx(io_ctx1)); + MockInstanceReplayer mock_instance_replayer1; + auto instance_watcher1 = MockInstanceWatcher::create( + io_ctx1, m_mock_threads->work_queue, &mock_instance_replayer1); + + librados::Rados cluster; + librados::IoCtx io_ctx2; + EXPECT_EQ("", connect_cluster_pp(cluster)); + EXPECT_EQ(0, cluster.ioctx_create(_local_pool_name.c_str(), io_ctx2)); + std::string instance_id2 = stringify(io_ctx2.get_instance_id()); + librados::MockTestMemIoCtxImpl &mock_io_ctx2(get_mock_io_ctx(io_ctx2)); + MockInstanceReplayer mock_instance_replayer2; + auto instance_watcher2 = MockInstanceWatcher::create( + io_ctx2, m_mock_threads->work_queue, &mock_instance_replayer2); + + InSequence seq; + + // Init instance watcher 1 + expect_register_instance(mock_io_ctx1, 0); + expect_register_watch(mock_io_ctx1, instance_id1); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher1->init()); + + // Init instance watcher 2 + expect_register_instance(mock_io_ctx2, 0); + expect_register_watch(mock_io_ctx2, instance_id2); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher2->init()); + + // Acquire Image on the the same instance + EXPECT_CALL(mock_instance_replayer1, acquire_image(instance_watcher1, "gid", + _)) + .WillOnce(WithArg<2>(CompleteContext(0))); + C_SaferCond on_acquire1; + instance_watcher1->notify_image_acquire(instance_id1, "gid", &on_acquire1); + ASSERT_EQ(0, on_acquire1.wait()); + + // Acquire Image on the other instance + EXPECT_CALL(mock_instance_replayer2, acquire_image(instance_watcher2, "gid", + _)) + .WillOnce(WithArg<2>(CompleteContext(0))); + C_SaferCond on_acquire2; + instance_watcher1->notify_image_acquire(instance_id2, "gid", &on_acquire2); + ASSERT_EQ(0, on_acquire2.wait()); + + // Release Image on the the same instance + EXPECT_CALL(mock_instance_replayer1, release_image("gid", _)) + .WillOnce(WithArg<1>(CompleteContext(0))); + C_SaferCond on_release1; + instance_watcher1->notify_image_release(instance_id1, "gid", &on_release1); + ASSERT_EQ(0, on_release1.wait()); + + // Release Image on the other instance + EXPECT_CALL(mock_instance_replayer2, release_image("gid", _)) + .WillOnce(WithArg<1>(CompleteContext(0))); + C_SaferCond on_release2; + instance_watcher1->notify_image_release(instance_id2, "gid", &on_release2); + ASSERT_EQ(0, on_release2.wait()); + + // Shutdown instance watcher 1 + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx1); + expect_unregister_instance(mock_io_ctx1, 0); + instance_watcher1->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher1; + + // Shutdown instance watcher 2 + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx2); + expect_unregister_instance(mock_io_ctx2, 0); + instance_watcher2->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher2; +} + +TEST_F(TestMockInstanceWatcher, PeerImageRemoved) { + MockManagedLock mock_managed_lock; + + librados::IoCtx& io_ctx1 = m_local_io_ctx; + std::string instance_id1 = m_instance_id; + librados::MockTestMemIoCtxImpl &mock_io_ctx1(get_mock_io_ctx(io_ctx1)); + MockInstanceReplayer mock_instance_replayer1; + auto instance_watcher1 = MockInstanceWatcher::create( + io_ctx1, m_mock_threads->work_queue, &mock_instance_replayer1); + + librados::Rados cluster; + librados::IoCtx io_ctx2; + EXPECT_EQ("", connect_cluster_pp(cluster)); + EXPECT_EQ(0, cluster.ioctx_create(_local_pool_name.c_str(), io_ctx2)); + std::string instance_id2 = stringify(io_ctx2.get_instance_id()); + librados::MockTestMemIoCtxImpl &mock_io_ctx2(get_mock_io_ctx(io_ctx2)); + MockInstanceReplayer mock_instance_replayer2; + auto instance_watcher2 = MockInstanceWatcher::create( + io_ctx2, m_mock_threads->work_queue, &mock_instance_replayer2); + + InSequence seq; + + // Init instance watcher 1 + expect_register_instance(mock_io_ctx1, 0); + expect_register_watch(mock_io_ctx1, instance_id1); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher1->init()); + + // Init instance watcher 2 + expect_register_instance(mock_io_ctx2, 0); + expect_register_watch(mock_io_ctx2, instance_id2); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher2->init()); + + // Peer Image Removed on the same instance + EXPECT_CALL(mock_instance_replayer1, remove_peer_image("gid", "uuid", _)) + .WillOnce(WithArg<2>(CompleteContext(0))); + C_SaferCond on_removed1; + instance_watcher1->notify_peer_image_removed(instance_id1, "gid", "uuid", + &on_removed1); + ASSERT_EQ(0, on_removed1.wait()); + + // Peer Image Removed on the other instance + EXPECT_CALL(mock_instance_replayer2, remove_peer_image("gid", "uuid", _)) + .WillOnce(WithArg<2>(CompleteContext(0))); + C_SaferCond on_removed2; + instance_watcher1->notify_peer_image_removed(instance_id2, "gid", "uuid", + &on_removed2); + ASSERT_EQ(0, on_removed2.wait()); + + // Shutdown instance watcher 1 + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx1); + expect_unregister_instance(mock_io_ctx1, 0); + instance_watcher1->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher1; + + // Shutdown instance watcher 2 + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx2); + expect_unregister_instance(mock_io_ctx2, 0); + instance_watcher2->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher2; +} + +TEST_F(TestMockInstanceWatcher, ImageAcquireReleaseCancel) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + auto instance_watcher = new MockInstanceWatcher( + m_local_io_ctx, m_mock_threads->work_queue, nullptr, m_instance_id); + InSequence seq; + + // Init + expect_register_instance(mock_io_ctx, 0); + expect_register_watch(mock_io_ctx); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher->init()); + + // Send Acquire Image and cancel + EXPECT_CALL(mock_io_ctx, aio_notify(_, _, _, _, _)) + .WillOnce(Invoke( + [this, instance_watcher, &mock_io_ctx]( + const std::string& o, librados::AioCompletionImpl *c, + bufferlist& bl, uint64_t timeout_ms, bufferlist *pbl) { + c->get(); + auto ctx = new FunctionContext( + [instance_watcher, &mock_io_ctx, c, pbl](int r) { + instance_watcher->cancel_notify_requests("other"); + encode(librbd::watcher::NotifyResponse(), *pbl); + mock_io_ctx.get_mock_rados_client()-> + finish_aio_completion(c, -ETIMEDOUT); + }); + m_threads->work_queue->queue(ctx, 0); + })); + + C_SaferCond on_acquire; + instance_watcher->notify_image_acquire("other", "gid", &on_acquire); + ASSERT_EQ(-ECANCELED, on_acquire.wait()); + + // Send Release Image and cancel + EXPECT_CALL(mock_io_ctx, aio_notify(_, _, _, _, _)) + .WillOnce(Invoke( + [this, instance_watcher, &mock_io_ctx]( + const std::string& o, librados::AioCompletionImpl *c, + bufferlist& bl, uint64_t timeout_ms, bufferlist *pbl) { + c->get(); + auto ctx = new FunctionContext( + [instance_watcher, &mock_io_ctx, c, pbl](int r) { + instance_watcher->cancel_notify_requests("other"); + encode(librbd::watcher::NotifyResponse(), *pbl); + mock_io_ctx.get_mock_rados_client()-> + finish_aio_completion(c, -ETIMEDOUT); + }); + m_threads->work_queue->queue(ctx, 0); + })); + + C_SaferCond on_release; + instance_watcher->notify_image_release("other", "gid", &on_release); + ASSERT_EQ(-ECANCELED, on_release.wait()); + + // Shutdown + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx); + expect_unregister_instance(mock_io_ctx, 0); + instance_watcher->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher; +} + +TEST_F(TestMockInstanceWatcher, PeerImageAcquireWatchDNE) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + MockInstanceReplayer mock_instance_replayer; + auto instance_watcher = new MockInstanceWatcher( + m_local_io_ctx, m_mock_threads->work_queue, &mock_instance_replayer, + m_instance_id); + InSequence seq; + + // Init + expect_register_instance(mock_io_ctx, 0); + expect_register_watch(mock_io_ctx); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher->init()); + + // Acquire image on dead (blacklisted) instance + C_SaferCond on_acquire; + instance_watcher->notify_image_acquire("dead instance", "global image id", + &on_acquire); + ASSERT_EQ(-ENOENT, on_acquire.wait()); + + // Shutdown + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx); + expect_unregister_instance(mock_io_ctx, 0); + instance_watcher->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher; +} + +TEST_F(TestMockInstanceWatcher, PeerImageReleaseWatchDNE) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + MockInstanceReplayer mock_instance_replayer; + auto instance_watcher = new MockInstanceWatcher( + m_local_io_ctx, m_mock_threads->work_queue, &mock_instance_replayer, + m_instance_id); + InSequence seq; + + // Init + expect_register_instance(mock_io_ctx, 0); + expect_register_watch(mock_io_ctx); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher->init()); + + // Release image on dead (blacklisted) instance + C_SaferCond on_acquire; + instance_watcher->notify_image_release("dead instance", "global image id", + &on_acquire); + ASSERT_EQ(-ENOENT, on_acquire.wait()); + + // Shutdown + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx); + expect_unregister_instance(mock_io_ctx, 0); + instance_watcher->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher; +} + +TEST_F(TestMockInstanceWatcher, PeerImageRemovedCancel) { + MockManagedLock mock_managed_lock; + librados::MockTestMemIoCtxImpl &mock_io_ctx(get_mock_io_ctx(m_local_io_ctx)); + + auto instance_watcher = new MockInstanceWatcher( + m_local_io_ctx, m_mock_threads->work_queue, nullptr, m_instance_id); + InSequence seq; + + // Init + expect_register_instance(mock_io_ctx, 0); + expect_register_watch(mock_io_ctx); + expect_acquire_lock(mock_managed_lock, 0); + ASSERT_EQ(0, instance_watcher->init()); + + // Send Acquire Image and cancel + EXPECT_CALL(mock_io_ctx, aio_notify(_, _, _, _, _)) + .WillOnce(Invoke( + [this, instance_watcher, &mock_io_ctx]( + const std::string& o, librados::AioCompletionImpl *c, + bufferlist& bl, uint64_t timeout_ms, bufferlist *pbl) { + c->get(); + auto ctx = new FunctionContext( + [instance_watcher, &mock_io_ctx, c, pbl](int r) { + instance_watcher->cancel_notify_requests("other"); + encode(librbd::watcher::NotifyResponse(), *pbl); + mock_io_ctx.get_mock_rados_client()-> + finish_aio_completion(c, -ETIMEDOUT); + }); + m_threads->work_queue->queue(ctx, 0); + })); + + C_SaferCond on_acquire; + instance_watcher->notify_peer_image_removed("other", "gid", "uuid", + &on_acquire); + ASSERT_EQ(-ECANCELED, on_acquire.wait()); + + // Shutdown + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx); + expect_unregister_instance(mock_io_ctx, 0); + instance_watcher->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher; +} + +class TestMockInstanceWatcher_NotifySync : public TestMockInstanceWatcher { +public: + typedef ImageSyncThrottler MockImageSyncThrottler; + + MockManagedLock mock_managed_lock; + MockImageSyncThrottler mock_image_sync_throttler; + std::string instance_id1; + std::string instance_id2; + + librados::Rados cluster; + librados::IoCtx io_ctx2; + + MockInstanceWatcher *instance_watcher1; + MockInstanceWatcher *instance_watcher2; + + void SetUp() override { + TestMockInstanceWatcher::SetUp(); + + instance_id1 = m_instance_id; + librados::IoCtx& io_ctx1 = m_local_io_ctx; + librados::MockTestMemIoCtxImpl &mock_io_ctx1(get_mock_io_ctx(io_ctx1)); + instance_watcher1 = MockInstanceWatcher::create(io_ctx1, + m_mock_threads->work_queue, + nullptr); + EXPECT_EQ("", connect_cluster_pp(cluster)); + EXPECT_EQ(0, cluster.ioctx_create(_local_pool_name.c_str(), io_ctx2)); + instance_id2 = stringify(io_ctx2.get_instance_id()); + librados::MockTestMemIoCtxImpl &mock_io_ctx2(get_mock_io_ctx(io_ctx2)); + instance_watcher2 = MockInstanceWatcher::create(io_ctx2, + m_mock_threads->work_queue, + nullptr); + InSequence seq; + + // Init instance watcher 1 (leader) + expect_register_instance(mock_io_ctx1, 0); + expect_register_watch(mock_io_ctx1, instance_id1); + expect_acquire_lock(mock_managed_lock, 0); + EXPECT_EQ(0, instance_watcher1->init()); + instance_watcher1->handle_acquire_leader(); + + // Init instance watcher 2 + expect_register_instance(mock_io_ctx2, 0); + expect_register_watch(mock_io_ctx2, instance_id2); + expect_acquire_lock(mock_managed_lock, 0); + EXPECT_EQ(0, instance_watcher2->init()); + instance_watcher2->handle_update_leader(instance_id1); + } + + void TearDown() override { + librados::IoCtx& io_ctx1 = m_local_io_ctx; + librados::MockTestMemIoCtxImpl &mock_io_ctx1(get_mock_io_ctx(io_ctx1)); + librados::MockTestMemIoCtxImpl &mock_io_ctx2(get_mock_io_ctx(io_ctx2)); + + InSequence seq; + + expect_throttler_destroy(); + instance_watcher1->handle_release_leader(); + + // Shutdown instance watcher 1 + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx1); + expect_unregister_instance(mock_io_ctx1, 0); + instance_watcher1->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher1; + + // Shutdown instance watcher 2 + expect_release_lock(mock_managed_lock, 0); + expect_unregister_watch(mock_io_ctx2); + expect_unregister_instance(mock_io_ctx2, 0); + instance_watcher2->shut_down(); + + expect_destroy_lock(mock_managed_lock); + delete instance_watcher2; + + TestMockInstanceWatcher::TearDown(); + } + + void expect_throttler_destroy( + std::vector *throttler_queue = nullptr) { + EXPECT_CALL(mock_image_sync_throttler, drain(-ESTALE)) + .WillOnce(Invoke([throttler_queue] (int r) { + if (throttler_queue != nullptr) { + for (auto ctx : *throttler_queue) { + ctx->complete(r); + } + } + })); + EXPECT_CALL(mock_image_sync_throttler, destroy()); + } + + void expect_throttler_start_op(const std::string &sync_id, + Context *on_call = nullptr, + Context **on_start_ctx = nullptr) { + EXPECT_CALL(mock_image_sync_throttler, start_op(sync_id, _)) + .WillOnce(Invoke([on_call, on_start_ctx] (const std::string &, + Context *ctx) { + if (on_start_ctx != nullptr) { + *on_start_ctx = ctx; + } else { + ctx->complete(0); + } + if (on_call != nullptr) { + on_call->complete(0); + } + })); + } + + void expect_throttler_finish_op(const std::string &sync_id, + Context *on_finish) { + EXPECT_CALL(mock_image_sync_throttler, finish_op("sync_id")) + .WillOnce(Invoke([on_finish](const std::string &) { + on_finish->complete(0); + })); + } +}; + +TEST_F(TestMockInstanceWatcher_NotifySync, StartStopOnLeader) { + InSequence seq; + + expect_throttler_start_op("sync_id"); + C_SaferCond on_start; + instance_watcher1->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start.wait()); + + C_SaferCond on_finish; + expect_throttler_finish_op("sync_id", &on_finish); + instance_watcher1->notify_sync_complete("sync_id"); + ASSERT_EQ(0, on_finish.wait()); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, CancelStartedOnLeader) { + InSequence seq; + + expect_throttler_start_op("sync_id"); + C_SaferCond on_start; + instance_watcher1->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start.wait()); + + ASSERT_FALSE(instance_watcher1->cancel_sync_request("sync_id")); + + C_SaferCond on_finish; + expect_throttler_finish_op("sync_id", &on_finish); + instance_watcher1->notify_sync_complete("sync_id"); + ASSERT_EQ(0, on_finish.wait()); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, StartStopOnNonLeader) { + InSequence seq; + + expect_throttler_start_op("sync_id"); + C_SaferCond on_start; + instance_watcher2->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start.wait()); + + C_SaferCond on_finish; + expect_throttler_finish_op("sync_id", &on_finish); + instance_watcher2->notify_sync_complete("sync_id"); + ASSERT_EQ(0, on_finish.wait()); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, CancelStartedOnNonLeader) { + InSequence seq; + + expect_throttler_start_op("sync_id"); + C_SaferCond on_start; + instance_watcher2->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start.wait()); + + ASSERT_FALSE(instance_watcher2->cancel_sync_request("sync_id")); + + C_SaferCond on_finish; + expect_throttler_finish_op("sync_id", &on_finish); + instance_watcher2->notify_sync_complete("sync_id"); + ASSERT_EQ(0, on_finish.wait()); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, CancelWaitingOnNonLeader) { + InSequence seq; + + C_SaferCond on_start_op_called; + Context *on_start_ctx; + expect_throttler_start_op("sync_id", &on_start_op_called, + &on_start_ctx); + C_SaferCond on_start; + instance_watcher2->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start_op_called.wait()); + + ASSERT_TRUE(instance_watcher2->cancel_sync_request("sync_id")); + // emulate watcher timeout + on_start_ctx->complete(-ETIMEDOUT); + ASSERT_EQ(-ECANCELED, on_start.wait()); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, InFlightPrevNotification) { + // start sync when previous notification is still in flight + + InSequence seq; + + expect_throttler_start_op("sync_id"); + C_SaferCond on_start1; + instance_watcher2->notify_sync_request("sync_id", &on_start1); + ASSERT_EQ(0, on_start1.wait()); + + C_SaferCond on_start2; + EXPECT_CALL(mock_image_sync_throttler, finish_op("sync_id")) + .WillOnce(Invoke([this, &on_start2](const std::string &) { + instance_watcher2->notify_sync_request("sync_id", &on_start2); + })); + expect_throttler_start_op("sync_id"); + instance_watcher2->notify_sync_complete("sync_id"); + + ASSERT_EQ(0, on_start2.wait()); + C_SaferCond on_finish; + expect_throttler_finish_op("sync_id", &on_finish); + instance_watcher2->notify_sync_complete("sync_id"); + ASSERT_EQ(0, on_finish.wait()); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, NoInFlightReleaseAcquireLeader) { + InSequence seq; + + expect_throttler_destroy(); + instance_watcher1->handle_release_leader(); + instance_watcher1->handle_acquire_leader(); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, StartedOnLeaderReleaseLeader) { + InSequence seq; + + expect_throttler_destroy(); + instance_watcher1->handle_release_leader(); + instance_watcher2->handle_acquire_leader(); + + expect_throttler_start_op("sync_id"); + C_SaferCond on_start; + instance_watcher2->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start.wait()); + expect_throttler_destroy(); + instance_watcher2->handle_release_leader(); + instance_watcher2->notify_sync_complete("sync_id"); + + instance_watcher1->handle_acquire_leader(); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, WaitingOnLeaderReleaseLeader) { + InSequence seq; + + C_SaferCond on_start_op_called; + Context *on_start_ctx; + expect_throttler_start_op("sync_id", &on_start_op_called, &on_start_ctx); + C_SaferCond on_start; + instance_watcher1->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start_op_called.wait()); + + std::vector throttler_queue = {on_start_ctx}; + expect_throttler_destroy(&throttler_queue); + instance_watcher1->handle_release_leader(); + expect_throttler_start_op("sync_id"); + instance_watcher2->handle_acquire_leader(); + instance_watcher1->handle_update_leader(instance_id2); + + ASSERT_EQ(0, on_start.wait()); + C_SaferCond on_finish; + expect_throttler_finish_op("sync_id", &on_finish); + instance_watcher1->notify_sync_complete("sync_id"); + ASSERT_EQ(0, on_finish.wait()); + + expect_throttler_destroy(); + instance_watcher2->handle_release_leader(); + instance_watcher1->handle_acquire_leader(); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, StartedOnNonLeaderAcquireLeader) { + InSequence seq; + + expect_throttler_destroy(); + instance_watcher1->handle_release_leader(); + instance_watcher2->handle_acquire_leader(); + instance_watcher1->handle_update_leader(instance_id2); + + expect_throttler_start_op("sync_id"); + C_SaferCond on_start; + instance_watcher1->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start.wait()); + + expect_throttler_destroy(); + instance_watcher2->handle_release_leader(); + instance_watcher1->handle_acquire_leader(); + instance_watcher2->handle_update_leader(instance_id1); + + instance_watcher1->notify_sync_complete("sync_id"); +} + +TEST_F(TestMockInstanceWatcher_NotifySync, WaitingOnNonLeaderAcquireLeader) { + InSequence seq; + + C_SaferCond on_start_op_called; + Context *on_start_ctx; + expect_throttler_start_op("sync_id", &on_start_op_called, + &on_start_ctx); + C_SaferCond on_start; + instance_watcher2->notify_sync_request("sync_id", &on_start); + ASSERT_EQ(0, on_start_op_called.wait()); + + std::vector throttler_queue = {on_start_ctx}; + expect_throttler_destroy(&throttler_queue); + instance_watcher1->handle_release_leader(); + + EXPECT_CALL(mock_image_sync_throttler, start_op("sync_id", _)) + .WillOnce(WithArg<1>(CompleteContext(0))); + instance_watcher2->handle_acquire_leader(); + instance_watcher1->handle_update_leader(instance_id2); + + ASSERT_EQ(0, on_start.wait()); + + C_SaferCond on_finish; + expect_throttler_finish_op("sync_id", &on_finish); + instance_watcher2->notify_sync_complete("sync_id"); + ASSERT_EQ(0, on_finish.wait()); + + expect_throttler_destroy(); + instance_watcher2->handle_release_leader(); + instance_watcher1->handle_acquire_leader(); +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_mock_LeaderWatcher.cc b/src/test/rbd_mirror/test_mock_LeaderWatcher.cc new file mode 100644 index 00000000..8074a5a5 --- /dev/null +++ b/src/test/rbd_mirror/test_mock_LeaderWatcher.cc @@ -0,0 +1,694 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/Utils.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/rbd_mirror/test_mock_fixture.h" +#include "tools/rbd_mirror/LeaderWatcher.h" +#include "tools/rbd_mirror/Threads.h" + +using librbd::util::create_async_context_callback; + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +struct MockManagedLock { + static MockManagedLock *s_instance; + static MockManagedLock &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + MockManagedLock() { + s_instance = this; + } + + bool m_release_lock_on_shutdown = false; + Context *m_on_released = nullptr; + + MOCK_METHOD0(construct, void()); + MOCK_METHOD0(destroy, void()); + + MOCK_CONST_METHOD0(is_lock_owner, bool()); + + MOCK_METHOD1(shut_down, void(Context *)); + MOCK_METHOD1(try_acquire_lock, void(Context *)); + MOCK_METHOD1(release_lock, void(Context *)); + MOCK_METHOD0(reacquire_lock, void()); + MOCK_METHOD3(break_lock, void(const managed_lock::Locker &, bool, Context *)); + MOCK_METHOD2(get_locker, void(managed_lock::Locker *, Context *)); + + MOCK_METHOD0(set_state_post_acquiring, void()); + + MOCK_CONST_METHOD0(is_shutdown, bool()); + + MOCK_CONST_METHOD0(is_state_post_acquiring, bool()); + MOCK_CONST_METHOD0(is_state_pre_releasing, bool()); + MOCK_CONST_METHOD0(is_state_locked, bool()); +}; + +MockManagedLock *MockManagedLock::s_instance = nullptr; + +template <> +struct ManagedLock { + ManagedLock(librados::IoCtx& ioctx, ContextWQ *work_queue, + const std::string& oid, librbd::Watcher *watcher, + managed_lock::Mode mode, bool blacklist_on_break_lock, + uint32_t blacklist_expire_seconds) + : m_work_queue(work_queue), m_lock("ManagedLock::m_lock") { + MockManagedLock::get_instance().construct(); + } + + virtual ~ManagedLock() { + MockManagedLock::get_instance().destroy(); + } + + ContextWQ *m_work_queue; + + mutable Mutex m_lock; + + bool is_lock_owner() const { + return MockManagedLock::get_instance().is_lock_owner(); + } + + void shut_down(Context *on_shutdown) { + if (MockManagedLock::get_instance().m_release_lock_on_shutdown) { + on_shutdown = new FunctionContext( + [this, on_shutdown](int r) { + MockManagedLock::get_instance().m_release_lock_on_shutdown = false; + shut_down(on_shutdown); + }); + release_lock(on_shutdown); + return; + } + + MockManagedLock::get_instance().shut_down(on_shutdown); + } + + void try_acquire_lock(Context *on_acquired) { + Context *post_acquire_ctx = create_async_context_callback( + m_work_queue, new FunctionContext( + [this, on_acquired](int r) { + post_acquire_lock_handler(r, on_acquired); + })); + MockManagedLock::get_instance().try_acquire_lock(post_acquire_ctx); + } + + void release_lock(Context *on_released) { + ceph_assert(MockManagedLock::get_instance().m_on_released == nullptr); + MockManagedLock::get_instance().m_on_released = on_released; + + Context *post_release_ctx = new FunctionContext( + [this](int r) { + ceph_assert(MockManagedLock::get_instance().m_on_released != nullptr); + post_release_lock_handler(false, r, + MockManagedLock::get_instance().m_on_released); + MockManagedLock::get_instance().m_on_released = nullptr; + }); + + Context *release_ctx = new FunctionContext( + [this, post_release_ctx](int r) { + if (r < 0) { + MockManagedLock::get_instance().m_on_released->complete(r); + } else { + MockManagedLock::get_instance().release_lock(post_release_ctx); + } + }); + + Context *pre_release_ctx = new FunctionContext( + [this, release_ctx](int r) { + bool shutting_down = + MockManagedLock::get_instance().m_release_lock_on_shutdown; + pre_release_lock_handler(shutting_down, release_ctx); + }); + + m_work_queue->queue(pre_release_ctx, 0); + } + + void reacquire_lock(Context* on_finish) { + MockManagedLock::get_instance().reacquire_lock(); + } + + void get_locker(managed_lock::Locker *locker, Context *on_finish) { + MockManagedLock::get_instance().get_locker(locker, on_finish); + } + + void break_lock(const managed_lock::Locker &locker, bool force_break_lock, + Context *on_finish) { + MockManagedLock::get_instance().break_lock(locker, force_break_lock, + on_finish); + } + + void set_state_post_acquiring() { + MockManagedLock::get_instance().set_state_post_acquiring(); + } + + bool is_shutdown() const { + return MockManagedLock::get_instance().is_shutdown(); + } + + bool is_state_post_acquiring() const { + return MockManagedLock::get_instance().is_state_post_acquiring(); + } + + bool is_state_pre_releasing() const { + return MockManagedLock::get_instance().is_state_pre_releasing(); + } + + bool is_state_locked() const { + return MockManagedLock::get_instance().is_state_locked(); + } + + virtual void post_acquire_lock_handler(int r, Context *on_finish) = 0; + virtual void pre_release_lock_handler(bool shutting_down, + Context *on_finish) = 0; + virtual void post_release_lock_handler(bool shutting_down, int r, + Context *on_finish) = 0; +}; + +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct Threads { + Mutex &timer_lock; + SafeTimer *timer; + ContextWQ *work_queue; + + Threads(Threads *threads) + : timer_lock(threads->timer_lock), timer(threads->timer), + work_queue(threads->work_queue) { + } +}; + +template <> +struct MirrorStatusWatcher { + static MirrorStatusWatcher* s_instance; + + static MirrorStatusWatcher *create(librados::IoCtx &io_ctx, + ContextWQ *work_queue) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MirrorStatusWatcher() { + ceph_assert(s_instance == nullptr); + s_instance = this; + } + + ~MirrorStatusWatcher() { + ceph_assert(s_instance == this); + s_instance = nullptr; + } + + MOCK_METHOD0(destroy, void()); + MOCK_METHOD1(init, void(Context *)); + MOCK_METHOD1(shut_down, void(Context *)); +}; + +MirrorStatusWatcher *MirrorStatusWatcher::s_instance = nullptr; + +template <> +struct Instances { + static Instances* s_instance; + + static Instances *create(Threads *threads, + librados::IoCtx &ioctx, + const std::string& instance_id, + instances::Listener&) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + Instances() { + ceph_assert(s_instance == nullptr); + s_instance = this; + } + + ~Instances() { + ceph_assert(s_instance == this); + s_instance = nullptr; + } + + MOCK_METHOD0(destroy, void()); + MOCK_METHOD1(init, void(Context *)); + MOCK_METHOD1(shut_down, void(Context *)); + MOCK_METHOD1(acked, void(const std::vector &)); + MOCK_METHOD0(unblock_listener, void()); +}; + +Instances *Instances::s_instance = nullptr; + +} // namespace mirror +} // namespace rbd + + +// template definitions +#include "tools/rbd_mirror/LeaderWatcher.cc" + +namespace rbd { +namespace mirror { + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; + +using librbd::MockManagedLock; + +struct MockListener : public leader_watcher::Listener { + static MockListener* s_instance; + + MockListener() { + ceph_assert(s_instance == nullptr); + s_instance = this; + } + + ~MockListener() override { + ceph_assert(s_instance == this); + s_instance = nullptr; + } + + MOCK_METHOD1(post_acquire_handler, void(Context *)); + MOCK_METHOD1(pre_release_handler, void(Context *)); + + MOCK_METHOD1(update_leader_handler, void(const std::string &)); + MOCK_METHOD1(handle_instances_added, void(const InstanceIds&)); + MOCK_METHOD1(handle_instances_removed, void(const InstanceIds&)); +}; + +MockListener *MockListener::s_instance = nullptr; + +class TestMockLeaderWatcher : public TestMockFixture { +public: + typedef MirrorStatusWatcher MockMirrorStatusWatcher; + typedef Instances MockInstances; + typedef LeaderWatcher MockLeaderWatcher; + typedef Threads MockThreads; + + void SetUp() override { + TestMockFixture::SetUp(); + m_mock_threads = new MockThreads(m_threads); + } + + void TearDown() override { + delete m_mock_threads; + TestMockFixture::TearDown(); + } + + void expect_construct(MockManagedLock &mock_managed_lock) { + EXPECT_CALL(mock_managed_lock, construct()); + } + + void expect_destroy(MockManagedLock &mock_managed_lock) { + EXPECT_CALL(mock_managed_lock, destroy()); + } + + void expect_is_lock_owner(MockManagedLock &mock_managed_lock, bool owner) { + EXPECT_CALL(mock_managed_lock, is_lock_owner()) + .WillOnce(Return(owner)); + } + + void expect_shut_down(MockManagedLock &mock_managed_lock, + bool release_lock_on_shutdown, int r) { + mock_managed_lock.m_release_lock_on_shutdown = release_lock_on_shutdown; + EXPECT_CALL(mock_managed_lock, shut_down(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_try_acquire_lock(MockManagedLock &mock_managed_lock, int r) { + EXPECT_CALL(mock_managed_lock, try_acquire_lock(_)) + .WillOnce(CompleteContext(r)); + if (r == 0) { + expect_set_state_post_acquiring(mock_managed_lock); + } + } + + void expect_release_lock(MockManagedLock &mock_managed_lock, int r, + Context *on_finish = nullptr) { + EXPECT_CALL(mock_managed_lock, release_lock(_)) + .WillOnce(Invoke([on_finish, &mock_managed_lock, r](Context *ctx) { + if (on_finish != nullptr) { + auto on_released = mock_managed_lock.m_on_released; + ceph_assert(on_released != nullptr); + mock_managed_lock.m_on_released = new FunctionContext( + [on_released, on_finish](int r) { + on_released->complete(r); + on_finish->complete(r); + }); + } + ctx->complete(r); + })); + } + + void expect_get_locker(MockManagedLock &mock_managed_lock, + const librbd::managed_lock::Locker &locker, int r) { + EXPECT_CALL(mock_managed_lock, get_locker(_, _)) + .WillOnce(Invoke([r, locker](librbd::managed_lock::Locker *out, + Context *ctx) { + if (r == 0) { + *out = locker; + } + ctx->complete(r); + })); + } + + void expect_break_lock(MockManagedLock &mock_managed_lock, + const librbd::managed_lock::Locker &locker, int r, + Context *on_finish) { + EXPECT_CALL(mock_managed_lock, break_lock(locker, true, _)) + .WillOnce(Invoke([on_finish, r](const librbd::managed_lock::Locker &, + bool, Context *ctx) { + ctx->complete(r); + on_finish->complete(0); + })); + } + + void expect_set_state_post_acquiring(MockManagedLock &mock_managed_lock) { + EXPECT_CALL(mock_managed_lock, set_state_post_acquiring()); + } + + void expect_is_shutdown(MockManagedLock &mock_managed_lock) { + EXPECT_CALL(mock_managed_lock, is_shutdown()) + .Times(AtLeast(0)).WillRepeatedly(Return(false)); + } + + void expect_is_leader(MockManagedLock &mock_managed_lock, bool post_acquiring, + bool locked) { + EXPECT_CALL(mock_managed_lock, is_state_post_acquiring()) + .WillOnce(Return(post_acquiring)); + if (!post_acquiring) { + EXPECT_CALL(mock_managed_lock, is_state_locked()) + .WillOnce(Return(locked)); + } + } + + void expect_is_leader(MockManagedLock &mock_managed_lock) { + EXPECT_CALL(mock_managed_lock, is_state_post_acquiring()) + .Times(AtLeast(0)).WillRepeatedly(Return(false)); + EXPECT_CALL(mock_managed_lock, is_state_locked()) + .Times(AtLeast(0)).WillRepeatedly(Return(false)); + EXPECT_CALL(mock_managed_lock, is_state_pre_releasing()) + .Times(AtLeast(0)).WillRepeatedly(Return(false)); + } + + void expect_notify_heartbeat(MockManagedLock &mock_managed_lock, + Context *on_finish) { + // is_leader in notify_heartbeat + EXPECT_CALL(mock_managed_lock, is_state_post_acquiring()) + .WillOnce(Return(false)); + EXPECT_CALL(mock_managed_lock, is_state_locked()) + .WillOnce(Return(true)); + + // is_leader in handle_notify_heartbeat + EXPECT_CALL(mock_managed_lock, is_state_post_acquiring()) + .WillOnce(Return(false)); + EXPECT_CALL(mock_managed_lock, is_state_locked()) + .WillOnce(DoAll(Invoke([on_finish]() { + on_finish->complete(0); + }), + Return(true))); + } + + void expect_destroy(MockMirrorStatusWatcher &mock_mirror_status_watcher) { + EXPECT_CALL(mock_mirror_status_watcher, destroy()); + } + + void expect_init(MockMirrorStatusWatcher &mock_mirror_status_watcher, int r) { + EXPECT_CALL(mock_mirror_status_watcher, init(_)) + .WillOnce(CompleteContext(m_mock_threads->work_queue, r)); + } + + void expect_shut_down(MockMirrorStatusWatcher &mock_mirror_status_watcher, int r) { + EXPECT_CALL(mock_mirror_status_watcher, shut_down(_)) + .WillOnce(CompleteContext(m_mock_threads->work_queue, r)); + expect_destroy(mock_mirror_status_watcher); + } + + void expect_destroy(MockInstances &mock_instances) { + EXPECT_CALL(mock_instances, destroy()); + } + + void expect_init(MockInstances &mock_instances, int r) { + EXPECT_CALL(mock_instances, init(_)) + .WillOnce(CompleteContext(m_mock_threads->work_queue, r)); + } + + void expect_shut_down(MockInstances &mock_instances, int r) { + EXPECT_CALL(mock_instances, shut_down(_)) + .WillOnce(CompleteContext(m_mock_threads->work_queue, r)); + expect_destroy(mock_instances); + } + + void expect_acquire_notify(MockManagedLock &mock_managed_lock, + MockListener &mock_listener, int r) { + expect_is_leader(mock_managed_lock, true, false); + EXPECT_CALL(mock_listener, post_acquire_handler(_)) + .WillOnce(CompleteContext(r)); + expect_is_leader(mock_managed_lock, true, false); + } + + void expect_release_notify(MockManagedLock &mock_managed_lock, + MockListener &mock_listener, int r) { + expect_is_leader(mock_managed_lock, false, false); + EXPECT_CALL(mock_listener, pre_release_handler(_)) + .WillOnce(CompleteContext(r)); + expect_is_leader(mock_managed_lock, false, false); + } + + void expect_unblock_listener(MockInstances& mock_instances) { + EXPECT_CALL(mock_instances, unblock_listener()); + } + + void expect_instances_acked(MockInstances& mock_instances) { + EXPECT_CALL(mock_instances, acked(_)); + } + + MockThreads *m_mock_threads; +}; + +TEST_F(TestMockLeaderWatcher, InitShutdown) { + MockManagedLock mock_managed_lock; + MockMirrorStatusWatcher mock_mirror_status_watcher; + MockInstances mock_instances; + MockListener listener; + + expect_is_shutdown(mock_managed_lock); + expect_destroy(mock_managed_lock); + + InSequence seq; + + expect_construct(mock_managed_lock); + MockLeaderWatcher leader_watcher(m_mock_threads, m_local_io_ctx, &listener); + + // Init + expect_init(mock_mirror_status_watcher, 0); + C_SaferCond on_heartbeat_finish; + expect_is_leader(mock_managed_lock, false, false); + expect_try_acquire_lock(mock_managed_lock, 0); + expect_init(mock_instances, 0); + expect_acquire_notify(mock_managed_lock, listener, 0); + expect_unblock_listener(mock_instances); + expect_notify_heartbeat(mock_managed_lock, &on_heartbeat_finish); + expect_instances_acked(mock_instances); + + ASSERT_EQ(0, leader_watcher.init()); + ASSERT_EQ(0, on_heartbeat_finish.wait()); + + // Shutdown + expect_release_notify(mock_managed_lock, listener, 0); + expect_shut_down(mock_instances, 0); + expect_release_lock(mock_managed_lock, 0); + expect_shut_down(mock_managed_lock, true, 0); + expect_shut_down(mock_mirror_status_watcher, 0); + expect_is_leader(mock_managed_lock, false, false); + + leader_watcher.shut_down(); +} + +TEST_F(TestMockLeaderWatcher, InitReleaseShutdown) { + MockManagedLock mock_managed_lock; + MockMirrorStatusWatcher mock_mirror_status_watcher; + MockInstances mock_instances; + MockListener listener; + + expect_is_shutdown(mock_managed_lock); + expect_destroy(mock_managed_lock); + + InSequence seq; + + expect_construct(mock_managed_lock); + MockLeaderWatcher leader_watcher(m_mock_threads, m_local_io_ctx, &listener); + + // Init + expect_init(mock_mirror_status_watcher, 0); + C_SaferCond on_heartbeat_finish; + expect_is_leader(mock_managed_lock, false, false); + expect_try_acquire_lock(mock_managed_lock, 0); + expect_init(mock_instances, 0); + expect_acquire_notify(mock_managed_lock, listener, 0); + expect_unblock_listener(mock_instances); + expect_notify_heartbeat(mock_managed_lock, &on_heartbeat_finish); + expect_instances_acked(mock_instances); + + ASSERT_EQ(0, leader_watcher.init()); + ASSERT_EQ(0, on_heartbeat_finish.wait()); + + // Release + expect_is_leader(mock_managed_lock, false, true); + expect_release_notify(mock_managed_lock, listener, 0); + expect_shut_down(mock_instances, 0); + C_SaferCond on_release; + expect_release_lock(mock_managed_lock, 0, &on_release); + + leader_watcher.release_leader(); + ASSERT_EQ(0, on_release.wait()); + + // Shutdown + expect_shut_down(mock_managed_lock, false, 0); + expect_shut_down(mock_mirror_status_watcher, 0); + expect_is_leader(mock_managed_lock, false, false); + + leader_watcher.shut_down(); +} + +TEST_F(TestMockLeaderWatcher, InitStatusWatcherError) { + MockManagedLock mock_managed_lock; + MockMirrorStatusWatcher mock_mirror_status_watcher; + MockInstances mock_instances; + MockListener listener; + + expect_is_shutdown(mock_managed_lock); + expect_is_leader(mock_managed_lock); + expect_destroy(mock_managed_lock); + + InSequence seq; + + expect_construct(mock_managed_lock); + MockLeaderWatcher leader_watcher(m_mock_threads, m_local_io_ctx, &listener); + + // Init + expect_init(mock_mirror_status_watcher, -EINVAL); + ASSERT_EQ(-EINVAL, leader_watcher.init()); + + // Shutdown + expect_shut_down(mock_managed_lock, false, 0); + expect_shut_down(mock_mirror_status_watcher, 0); + expect_is_leader(mock_managed_lock, false, false); + + leader_watcher.shut_down(); +} + +TEST_F(TestMockLeaderWatcher, AcquireError) { + MockManagedLock mock_managed_lock; + MockMirrorStatusWatcher mock_mirror_status_watcher; + MockInstances mock_instances; + MockListener listener; + + expect_is_shutdown(mock_managed_lock); + expect_is_leader(mock_managed_lock); + expect_destroy(mock_managed_lock); + + InSequence seq; + + expect_construct(mock_managed_lock); + MockLeaderWatcher leader_watcher(m_mock_threads, m_local_io_ctx, &listener); + + // Init + expect_init(mock_mirror_status_watcher, 0); + C_SaferCond on_heartbeat_finish; + expect_is_leader(mock_managed_lock, false, false); + expect_try_acquire_lock(mock_managed_lock, -EAGAIN); + expect_get_locker(mock_managed_lock, librbd::managed_lock::Locker(), -ENOENT); + expect_try_acquire_lock(mock_managed_lock, 0); + expect_init(mock_instances, 0); + expect_acquire_notify(mock_managed_lock, listener, 0); + expect_unblock_listener(mock_instances); + expect_notify_heartbeat(mock_managed_lock, &on_heartbeat_finish); + expect_instances_acked(mock_instances); + + ASSERT_EQ(0, leader_watcher.init()); + ASSERT_EQ(0, on_heartbeat_finish.wait()); + + // Shutdown + expect_release_notify(mock_managed_lock, listener, 0); + expect_shut_down(mock_instances, 0); + expect_release_lock(mock_managed_lock, 0); + expect_shut_down(mock_managed_lock, true, 0); + expect_shut_down(mock_mirror_status_watcher, 0); + expect_is_leader(mock_managed_lock, false, false); + + leader_watcher.shut_down(); +} + +TEST_F(TestMockLeaderWatcher, Break) { + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_leader_heartbeat_interval", "1")); + EXPECT_EQ(0, _rados->conf_set("rbd_mirror_leader_max_missed_heartbeats", + "1")); + CephContext *cct = reinterpret_cast(m_local_io_ctx.cct()); + int max_acquire_attempts = cct->_conf.get_val( + "rbd_mirror_leader_max_acquire_attempts_before_break"); + + MockManagedLock mock_managed_lock; + MockMirrorStatusWatcher mock_mirror_status_watcher; + MockInstances mock_instances; + MockListener listener; + librbd::managed_lock::Locker + locker{entity_name_t::CLIENT(1), "auto 123", "1.2.3.4:0/0", 123}; + + expect_is_shutdown(mock_managed_lock); + expect_is_leader(mock_managed_lock); + expect_destroy(mock_managed_lock); + EXPECT_CALL(listener, update_leader_handler(_)); + + InSequence seq; + + expect_construct(mock_managed_lock); + MockLeaderWatcher leader_watcher(m_mock_threads, m_local_io_ctx, &listener); + + // Init + expect_init(mock_mirror_status_watcher, 0); + expect_is_leader(mock_managed_lock, false, false); + for (int i = 0; i < max_acquire_attempts; i++) { + expect_try_acquire_lock(mock_managed_lock, -EAGAIN); + expect_get_locker(mock_managed_lock, locker, 0); + } + C_SaferCond on_break; + expect_break_lock(mock_managed_lock, locker, 0, &on_break); + C_SaferCond on_heartbeat_finish; + expect_try_acquire_lock(mock_managed_lock, 0); + expect_init(mock_instances, 0); + expect_acquire_notify(mock_managed_lock, listener, 0); + expect_unblock_listener(mock_instances); + expect_notify_heartbeat(mock_managed_lock, &on_heartbeat_finish); + expect_instances_acked(mock_instances); + + ASSERT_EQ(0, leader_watcher.init()); + ASSERT_EQ(0, on_heartbeat_finish.wait()); + + // Shutdown + expect_release_notify(mock_managed_lock, listener, 0); + expect_shut_down(mock_instances, 0); + expect_release_lock(mock_managed_lock, 0); + expect_shut_down(mock_managed_lock, true, 0); + expect_shut_down(mock_mirror_status_watcher, 0); + expect_is_leader(mock_managed_lock, false, false); + + leader_watcher.shut_down(); +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_mock_PoolReplayer.cc b/src/test/rbd_mirror/test_mock_PoolReplayer.cc new file mode 100644 index 00000000..e6bc9b1e --- /dev/null +++ b/src/test/rbd_mirror/test_mock_PoolReplayer.cc @@ -0,0 +1,468 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/api/Config.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librados_test_stub/MockTestMemCluster.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/rbd_mirror/test_mock_fixture.h" +#include "test/rbd_mirror/mock/MockContextWQ.h" +#include "test/rbd_mirror/mock/MockSafeTimer.h" +#include "tools/rbd_mirror/PoolReplayer.h" +#include "tools/rbd_mirror/ImageDeleter.h" +#include "tools/rbd_mirror/ImageMap.h" +#include "tools/rbd_mirror/InstanceWatcher.h" +#include "tools/rbd_mirror/InstanceReplayer.h" +#include "tools/rbd_mirror/LeaderWatcher.h" +#include "tools/rbd_mirror/PoolWatcher.h" +#include "tools/rbd_mirror/ServiceDaemon.h" +#include "tools/rbd_mirror/Threads.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace api { + +template <> +class Config { +public: + static void apply_pool_overrides(librados::IoCtx& io_ctx, + ConfigProxy* config_proxy) { + } +}; + +} + +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct ImageDeleter { + static ImageDeleter* s_instance; + + static ImageDeleter* create(librados::IoCtx &ioctx, + Threads *threads, + ServiceDaemon *service_daemon) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MOCK_METHOD1(init, void(Context*)); + MOCK_METHOD1(shut_down, void(Context*)); + MOCK_METHOD2(print_status, void(Formatter*, std::stringstream*)); + + ImageDeleter() { + s_instance = this; + } +}; + +ImageDeleter* ImageDeleter::s_instance = nullptr; + +template<> +struct ImageMap { + static ImageMap* s_instance; + + static ImageMap *create(librados::IoCtx &ioctx, + Threads *threads, + const std::string& instance_id, + image_map::Listener &listener) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MOCK_METHOD1(init, void(Context*)); + MOCK_METHOD1(shut_down, void(Context*)); + + MOCK_METHOD1(update_instances_added, void(const std::vector&)); + MOCK_METHOD1(update_instances_removed, void(const std::vector&)); + + MOCK_METHOD3(update_images_mock, void(const std::string&, + const std::set&, + const std::set&)); + void update_images(const std::string& mirror_uuid, + std::set&& added, + std::set&& removed) { + update_images_mock(mirror_uuid, added, removed); + } + + ImageMap() { + s_instance = this; + } +}; + +ImageMap* ImageMap::s_instance = nullptr; + +template<> +struct InstanceReplayer { + static InstanceReplayer* s_instance; + + static InstanceReplayer* create(Threads *threads, + ServiceDaemon *service_daemon, + RadosRef rados, const std::string& uuid, + int64_t pool_id) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MOCK_METHOD0(is_blacklisted, bool()); + + MOCK_METHOD0(start, void()); + MOCK_METHOD0(stop, void()); + MOCK_METHOD0(restart, void()); + MOCK_METHOD0(flush, void()); + + MOCK_METHOD2(print_status, void(Formatter*, std::stringstream*)); + + MOCK_METHOD2(add_peer, void(const std::string&, librados::IoCtx&)); + + MOCK_METHOD0(init, void()); + MOCK_METHOD0(shut_down, void()); + MOCK_METHOD1(release_all, void(Context*)); + + InstanceReplayer() { + s_instance = this; + } +}; + +InstanceReplayer* InstanceReplayer::s_instance = nullptr; + +template<> +struct InstanceWatcher { + static InstanceWatcher* s_instance; + + static InstanceWatcher* create(librados::IoCtx &ioctx, + MockContextWQ* work_queue, + InstanceReplayer* instance_replayer) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MOCK_METHOD0(handle_acquire_leader, void()); + MOCK_METHOD0(handle_release_leader, void()); + + MOCK_METHOD0(get_instance_id, std::string()); + + MOCK_METHOD2(print_sync_status, void(Formatter*, std::stringstream*)); + + MOCK_METHOD0(init, int()); + MOCK_METHOD0(shut_down, void()); + + MOCK_METHOD3(notify_image_acquire, void(const std::string&, + const std::string&, + Context*)); + MOCK_METHOD3(notify_image_release, void(const std::string&, + const std::string&, + Context*)); + MOCK_METHOD4(notify_peer_image_removed, void(const std::string&, + const std::string&, + const std::string&, + Context*)); + + MOCK_METHOD1(handle_update_leader, void(const std::string&)); + + InstanceWatcher() { + s_instance = this; + } + +}; + +InstanceWatcher* InstanceWatcher::s_instance = nullptr; + +template<> +struct LeaderWatcher { + static LeaderWatcher* s_instance; + + static LeaderWatcher *create(Threads *threads, + librados::IoCtx &ioctx, + leader_watcher::Listener* listener) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MOCK_METHOD0(is_blacklisted, bool()); + MOCK_METHOD0(is_leader, bool()); + MOCK_METHOD0(release_leader, void()); + + MOCK_METHOD1(get_leader_instance_id, void(std::string*)); + MOCK_METHOD1(list_instances, void(std::vector*)); + + MOCK_METHOD0(init, int()); + MOCK_METHOD0(shut_down, int()); + + LeaderWatcher() { + s_instance = this; + } + +}; + +LeaderWatcher* LeaderWatcher::s_instance = nullptr; + +template<> +struct PoolWatcher { + static PoolWatcher* s_instance; + + static PoolWatcher *create(Threads *threads, + librados::IoCtx &ioctx, + pool_watcher::Listener& listener) { + ceph_assert(s_instance != nullptr); + return s_instance; + } + + MOCK_METHOD0(is_blacklisted, bool()); + + MOCK_METHOD0(get_image_count, uint64_t()); + + MOCK_METHOD1(init, void(Context*)); + MOCK_METHOD1(shut_down, void(Context*)); + + PoolWatcher() { + s_instance = this; + } + +}; + +PoolWatcher* PoolWatcher::s_instance = nullptr; + +template<> +struct ServiceDaemon { + MOCK_METHOD3(add_or_update_attribute, + void(int64_t, const std::string&, + const service_daemon::AttributeValue&)); + MOCK_METHOD2(remove_attribute, + void(int64_t, const std::string&)); + + MOCK_METHOD4(add_or_update_callout, uint64_t(int64_t, uint64_t, + service_daemon::CalloutLevel, + const std::string&)); + MOCK_METHOD2(remove_callout, void(int64_t, uint64_t)); +}; + +template <> +struct Threads { + MockSafeTimer *timer; + Mutex &timer_lock; + Cond timer_cond; + + MockContextWQ *work_queue; + + Threads(Threads *threads) + : timer(new MockSafeTimer()), + timer_lock(threads->timer_lock), + work_queue(new MockContextWQ()) { + } + ~Threads() { + delete timer; + delete work_queue; + } +}; + +} // namespace mirror +} // namespace rbd + +// template definitions +#include "tools/rbd_mirror/PoolReplayer.cc" + +namespace rbd { +namespace mirror { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockPoolReplayer : public TestMockFixture { +public: + typedef PoolReplayer MockPoolReplayer; + typedef ImageMap MockImageMap; + typedef InstanceReplayer MockInstanceReplayer; + typedef InstanceWatcher MockInstanceWatcher; + typedef LeaderWatcher MockLeaderWatcher; + typedef PoolWatcher MockPoolWatcher; + typedef ServiceDaemon MockServiceDaemon; + typedef Threads MockThreads; + + void expect_connect(librados::MockTestMemCluster& mock_cluster, + librados::MockTestMemRadosClient* mock_rados_client, + const std::string& cluster_name, CephContext** cct_ref) { + EXPECT_CALL(mock_cluster, create_rados_client(_)) + .WillOnce(Invoke([cluster_name, mock_rados_client, cct_ref](CephContext* cct) { + EXPECT_EQ(cluster_name, cct->_conf->cluster); + if (cct_ref != nullptr) { + cct->get(); + *cct_ref = cct; + } + + return mock_rados_client; + })); + } + + void expect_create_ioctx(librados::MockTestMemRadosClient* mock_rados_client, + librados::MockTestMemIoCtxImpl* mock_io_ctx_impl) { + EXPECT_CALL(*mock_rados_client, create_ioctx(_, _)) + .WillOnce(Invoke([mock_io_ctx_impl](int64_t id, const std::string& name) { + return mock_io_ctx_impl; + })); + } + + void expect_mirror_uuid_get(librados::MockTestMemIoCtxImpl *io_ctx_impl, + const std::string &uuid, int r) { + bufferlist out_bl; + encode(uuid, out_bl); + + EXPECT_CALL(*io_ctx_impl, + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_uuid_get"), + _, _, _)) + .WillOnce(DoAll(WithArg<5>(Invoke([out_bl](bufferlist *bl) { + *bl = out_bl; + })), + Return(r))); + } + + void expect_instance_replayer_is_blacklisted( + MockInstanceReplayer &mock_instance_replayer, bool blacklisted) { + EXPECT_CALL(mock_instance_replayer, is_blacklisted()) + .WillRepeatedly(Return(blacklisted)); + } + + void expect_instance_replayer_init(MockInstanceReplayer& mock_instance_replayer) { + EXPECT_CALL(mock_instance_replayer, init()); + } + + void expect_instance_replayer_shut_down(MockInstanceReplayer& mock_instance_replayer) { + EXPECT_CALL(mock_instance_replayer, shut_down()); + } + + void expect_instance_replayer_stop(MockInstanceReplayer& mock_instance_replayer) { + EXPECT_CALL(mock_instance_replayer, stop()); + } + + void expect_instance_replayer_add_peer(MockInstanceReplayer& mock_instance_replayer, + const std::string& uuid) { + EXPECT_CALL(mock_instance_replayer, add_peer(uuid, _)); + } + + void expect_instance_watcher_get_instance_id( + MockInstanceWatcher& mock_instance_watcher, + const std::string &instance_id) { + EXPECT_CALL(mock_instance_watcher, get_instance_id()) + .WillOnce(Return(instance_id)); + } + + void expect_instance_watcher_init(MockInstanceWatcher& mock_instance_watcher, + int r) { + EXPECT_CALL(mock_instance_watcher, init()) + .WillOnce(Return(r)); + } + + void expect_instance_watcher_shut_down(MockInstanceWatcher& mock_instance_watcher) { + EXPECT_CALL(mock_instance_watcher, shut_down()); + } + + void expect_leader_watcher_is_blacklisted( + MockLeaderWatcher &mock_leader_watcher, bool blacklisted) { + EXPECT_CALL(mock_leader_watcher, is_blacklisted()) + .WillRepeatedly(Return(blacklisted)); + } + + void expect_leader_watcher_init(MockLeaderWatcher& mock_leader_watcher, + int r) { + EXPECT_CALL(mock_leader_watcher, init()) + .WillOnce(Return(r)); + } + + void expect_leader_watcher_shut_down(MockLeaderWatcher& mock_leader_watcher) { + EXPECT_CALL(mock_leader_watcher, shut_down()); + } + + void expect_service_daemon_add_or_update_attribute( + MockServiceDaemon &mock_service_daemon, const std::string& key, + const service_daemon::AttributeValue& value) { + EXPECT_CALL(mock_service_daemon, add_or_update_attribute(_, _, _)); + } + + void expect_service_daemon_add_or_update_instance_id_attribute( + MockInstanceWatcher& mock_instance_watcher, + MockServiceDaemon &mock_service_daemon) { + expect_instance_watcher_get_instance_id(mock_instance_watcher, "1234"); + expect_service_daemon_add_or_update_attribute(mock_service_daemon, + "instance_id", "1234"); + } +}; + +TEST_F(TestMockPoolReplayer, ConfigKeyOverride) { + PeerSpec peer_spec{"uuid", "cluster name", "client.name"}; + peer_spec.mon_host = "123"; + peer_spec.key = "234"; + + auto mock_instance_replayer = new MockInstanceReplayer(); + expect_instance_replayer_is_blacklisted(*mock_instance_replayer, false); + + auto mock_leader_watcher = new MockLeaderWatcher(); + expect_leader_watcher_is_blacklisted(*mock_leader_watcher, false); + + InSequence seq; + + auto& mock_cluster = get_mock_cluster(); + auto mock_local_rados_client = mock_cluster.do_create_rados_client( + g_ceph_context); + expect_connect(mock_cluster, mock_local_rados_client, "ceph", nullptr); + + auto mock_remote_rados_client = mock_cluster.do_create_rados_client( + g_ceph_context); + CephContext* remote_cct = nullptr; + expect_connect(mock_cluster, mock_remote_rados_client, "cluster name", + &remote_cct); + + auto mock_local_io_ctx = mock_local_rados_client->do_create_ioctx( + m_local_io_ctx.get_id(), m_local_io_ctx.get_pool_name()); + expect_create_ioctx(mock_local_rados_client, mock_local_io_ctx); + + expect_mirror_uuid_get(mock_local_io_ctx, "uuid", 0); + + expect_instance_replayer_init(*mock_instance_replayer); + expect_instance_replayer_add_peer(*mock_instance_replayer, "uuid"); + + auto mock_instance_watcher = new MockInstanceWatcher(); + expect_instance_watcher_init(*mock_instance_watcher, 0); + + MockServiceDaemon mock_service_daemon; + expect_service_daemon_add_or_update_instance_id_attribute( + *mock_instance_watcher, mock_service_daemon); + + expect_leader_watcher_init(*mock_leader_watcher, 0); + + MockThreads mock_threads(m_threads); + MockPoolReplayer pool_replayer(&mock_threads, &mock_service_daemon, + m_local_io_ctx.get_id(), peer_spec, {}); + pool_replayer.init(); + + ASSERT_TRUE(remote_cct != nullptr); + ASSERT_EQ("123", remote_cct->_conf.get_val("mon_host")); + ASSERT_EQ("234", remote_cct->_conf.get_val("key")); + remote_cct->put(); + + expect_instance_replayer_stop(*mock_instance_replayer); + expect_leader_watcher_shut_down(*mock_leader_watcher); + expect_instance_watcher_shut_down(*mock_instance_watcher); + expect_instance_replayer_shut_down(*mock_instance_replayer); + + pool_replayer.shut_down(); +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_mock_PoolWatcher.cc b/src/test/rbd_mirror/test_mock_PoolWatcher.cc new file mode 100644 index 00000000..b4dd66e8 --- /dev/null +++ b/src/test/rbd_mirror/test_mock_PoolWatcher.cc @@ -0,0 +1,897 @@ +// -*- 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 "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/rbd_mirror/mock/MockContextWQ.h" +#include "test/rbd_mirror/mock/MockSafeTimer.h" +#include "librbd/MirroringWatcher.h" +#include "tools/rbd_mirror/Threads.h" +#include "tools/rbd_mirror/PoolWatcher.h" +#include "tools/rbd_mirror/pool_watcher/RefreshImagesRequest.h" +#include "include/stringify.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public librbd::MockImageCtx { + MockTestImageCtx(librbd::ImageCtx &image_ctx) + : librbd::MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +struct MockMirroringWatcher { + static MockMirroringWatcher *s_instance; + static MockMirroringWatcher &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + MockMirroringWatcher() { + s_instance = this; + } + + MOCK_CONST_METHOD0(is_unregistered, bool()); + MOCK_METHOD1(register_watch, void(Context*)); + MOCK_METHOD1(unregister_watch, void(Context*)); + + MOCK_CONST_METHOD0(get_oid, std::string()); +}; + +template <> +struct MirroringWatcher { + static MirroringWatcher *s_instance; + + MirroringWatcher(librados::IoCtx &io_ctx, ::MockContextWQ *work_queue) { + s_instance = this; + } + virtual ~MirroringWatcher() { + } + + static MirroringWatcher &get_instance() { + ceph_assert(s_instance != nullptr); + return *s_instance; + } + + virtual void handle_rewatch_complete(int r) = 0; + + virtual void handle_mode_updated(cls::rbd::MirrorMode mirror_mode) = 0; + virtual void handle_image_updated(cls::rbd::MirrorImageState state, + const std::string &remote_image_id, + const std::string &global_image_id) = 0; + + bool is_unregistered() const { + return MockMirroringWatcher::get_instance().is_unregistered(); + } + void register_watch(Context *ctx) { + MockMirroringWatcher::get_instance().register_watch(ctx); + } + void unregister_watch(Context *ctx) { + MockMirroringWatcher::get_instance().unregister_watch(ctx); + } + std::string get_oid() const { + return MockMirroringWatcher::get_instance().get_oid(); + } +}; + +MockMirroringWatcher *MockMirroringWatcher::s_instance = nullptr; +MirroringWatcher *MirroringWatcher::s_instance = nullptr; + +} // namespace librbd + +namespace rbd { +namespace mirror { + +template <> +struct Threads { + MockSafeTimer *timer; + Mutex &timer_lock; + + MockContextWQ *work_queue; + + Threads(Threads *threads) + : timer(new MockSafeTimer()), + timer_lock(threads->timer_lock), + work_queue(new MockContextWQ()) { + } + ~Threads() { + delete timer; + delete work_queue; + } +}; + +namespace pool_watcher { + +template <> +struct RefreshImagesRequest { + ImageIds *image_ids = nullptr; + Context *on_finish = nullptr; + static RefreshImagesRequest *s_instance; + static RefreshImagesRequest *create(librados::IoCtx &io_ctx, + ImageIds *image_ids, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->image_ids = image_ids; + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RefreshImagesRequest() { + s_instance = this; + } +}; + +RefreshImagesRequest *RefreshImagesRequest::s_instance = nullptr; + +} // namespace pool_watcher + +} // namespace mirror +} // namespace rbd + +// template definitions +#include "tools/rbd_mirror/PoolWatcher.cc" + +namespace rbd { +namespace mirror { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnArg; +using ::testing::StrEq; +using ::testing::WithArg; +using ::testing::WithoutArgs; + +class TestMockPoolWatcher : public TestMockFixture { +public: + typedef PoolWatcher MockPoolWatcher; + typedef Threads MockThreads; + typedef pool_watcher::RefreshImagesRequest MockRefreshImagesRequest; + typedef librbd::MockMirroringWatcher MockMirroringWatcher; + typedef librbd::MirroringWatcher MirroringWatcher; + + struct MockListener : pool_watcher::Listener { + TestMockPoolWatcher *test; + + MockListener(TestMockPoolWatcher *test) : test(test) { + } + + MOCK_METHOD3(mock_handle_update, void(const std::string &, const ImageIds &, + const ImageIds &)); + void handle_update(const std::string &mirror_uuid, + ImageIds &&added_image_ids, + ImageIds &&removed_image_ids) override { + mock_handle_update(mirror_uuid, added_image_ids, removed_image_ids); + } + }; + + TestMockPoolWatcher() : m_lock("TestMockPoolWatcher::m_lock") { + } + + void expect_work_queue(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_mirroring_watcher_is_unregistered(MockMirroringWatcher &mock_mirroring_watcher, + bool unregistered) { + EXPECT_CALL(mock_mirroring_watcher, is_unregistered()) + .WillOnce(Return(unregistered)); + } + + void expect_mirroring_watcher_register(MockMirroringWatcher &mock_mirroring_watcher, + int r) { + EXPECT_CALL(mock_mirroring_watcher, register_watch(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_mirroring_watcher_unregister(MockMirroringWatcher &mock_mirroring_watcher, + int r) { + EXPECT_CALL(mock_mirroring_watcher, unregister_watch(_)) + .WillOnce(CompleteContext(r)); + } + + void expect_refresh_images(MockRefreshImagesRequest &request, + const ImageIds &image_ids, int r) { + EXPECT_CALL(request, send()) + .WillOnce(Invoke([&request, image_ids, r]() { + *request.image_ids = image_ids; + request.on_finish->complete(r); + })); + } + + void expect_listener_handle_update(MockListener &mock_listener, + const std::string &mirror_uuid, + const ImageIds &added_image_ids, + const ImageIds &removed_image_ids) { + EXPECT_CALL(mock_listener, mock_handle_update(mirror_uuid, added_image_ids, + removed_image_ids)) + .WillOnce(WithoutArgs(Invoke([this]() { + Mutex::Locker locker(m_lock); + ++m_update_count; + m_cond.Signal(); + }))); + } + + void expect_mirror_uuid_get(librados::IoCtx &io_ctx, + const std::string &uuid, int r) { + bufferlist out_bl; + encode(uuid, out_bl); + + EXPECT_CALL(get_mock_io_ctx(io_ctx), + exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_uuid_get"), + _, _, _)) + .WillOnce(DoAll(WithArg<5>(Invoke([out_bl](bufferlist *bl) { + *bl = out_bl; + })), + Return(r))); + } + + void expect_timer_add_event(MockThreads &mock_threads) { + EXPECT_CALL(*mock_threads.timer, add_event_after(_, _)) + .WillOnce(DoAll(WithArg<1>(Invoke([this](Context *ctx) { + auto wrapped_ctx = + new FunctionContext([this, ctx](int r) { + Mutex::Locker timer_locker(m_threads->timer_lock); + ctx->complete(r); + }); + m_threads->work_queue->queue(wrapped_ctx, 0); + })), + ReturnArg<1>())); + } + + int when_shut_down(MockPoolWatcher &mock_pool_watcher) { + C_SaferCond ctx; + mock_pool_watcher.shut_down(&ctx); + return ctx.wait(); + } + + bool wait_for_update(uint32_t count) { + Mutex::Locker locker(m_lock); + while (m_update_count < count) { + if (m_cond.WaitInterval(m_lock, utime_t(10, 0)) != 0) { + break; + } + } + if (m_update_count < count) { + return false; + } + + m_update_count -= count; + return true; + } + + Mutex m_lock; + Cond m_cond; + uint32_t m_update_count = 0; +}; + +TEST_F(TestMockPoolWatcher, EmptyPool) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, NonEmptyPool) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + ImageIds image_ids{ + {"global id 1", "remote id 1"}, + {"global id 2", "remote id 2"}}; + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, image_ids, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, NotifyDuringRefresh) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + ImageIds image_ids{ + {"global id 1", "remote id 1"}, + {"global id 2", "remote id 2"}}; + MockRefreshImagesRequest mock_refresh_images_request; + bool refresh_sent = false; + EXPECT_CALL(mock_refresh_images_request, send()) + .WillOnce(Invoke([this, &mock_refresh_images_request, &image_ids, + &refresh_sent]() { + *mock_refresh_images_request.image_ids = image_ids; + + Mutex::Locker locker(m_lock); + refresh_sent = true; + m_cond.Signal(); + })); + + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + image_ids = { + {"global id 1", "remote id 1a"}, + {"global id 3", "remote id 3"}}; + expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + mock_pool_watcher.init(nullptr); + + { + Mutex::Locker locker(m_lock); + while (!refresh_sent) { + m_cond.Wait(m_lock); + } + } + + MirroringWatcher::get_instance().handle_image_updated( + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, "remote id 2", "global id 2"); + MirroringWatcher::get_instance().handle_image_updated( + cls::rbd::MIRROR_IMAGE_STATE_ENABLED, "remote id 1a", "global id 1"); + MirroringWatcher::get_instance().handle_image_updated( + cls::rbd::MIRROR_IMAGE_STATE_ENABLED, "remote id 3", "global id 3"); + + mock_refresh_images_request.on_finish->complete(0); + ASSERT_TRUE(wait_for_update(1)); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, Notify) { + MockThreads mock_threads(m_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + ImageIds image_ids{ + {"global id 1", "remote id 1"}, + {"global id 2", "remote id 2"}}; + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, image_ids, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + EXPECT_CALL(*mock_threads.work_queue, queue(_, _)) + .WillOnce(Invoke([this](Context *ctx, int r) { + m_threads->work_queue->queue(ctx, r); + })); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {}); + + Context *notify_ctx = nullptr; + EXPECT_CALL(*mock_threads.work_queue, queue(_, _)) + .WillOnce(Invoke([this, ¬ify_ctx](Context *ctx, int r) { + Mutex::Locker locker(m_lock); + ASSERT_EQ(nullptr, notify_ctx); + notify_ctx = ctx; + m_cond.Signal(); + })); + expect_listener_handle_update( + mock_listener, "remote uuid", + {{"global id 1", "remote id 1a"}, {"global id 3", "remote id 3"}}, + {{"global id 1", "remote id 1"}, {"global id 2", "remote id 2"}}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(wait_for_update(1)); + + C_SaferCond flush_ctx; + m_threads->work_queue->queue(&flush_ctx, 0); + ASSERT_EQ(0, flush_ctx.wait()); + + MirroringWatcher::get_instance().handle_image_updated( + cls::rbd::MIRROR_IMAGE_STATE_DISABLING, "remote id 2", "global id 2"); + MirroringWatcher::get_instance().handle_image_updated( + cls::rbd::MIRROR_IMAGE_STATE_DISABLED, "remote id 2", "global id 2"); + MirroringWatcher::get_instance().handle_image_updated( + cls::rbd::MIRROR_IMAGE_STATE_ENABLED, "remote id 1a", "global id 1"); + MirroringWatcher::get_instance().handle_image_updated( + cls::rbd::MIRROR_IMAGE_STATE_ENABLED, "remote id 3", "global id 3"); + notify_ctx->complete(0); + + ASSERT_TRUE(wait_for_update(1)); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RegisterWatcherBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, -EBLACKLISTED); + + MockListener mock_listener(this); + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + ASSERT_TRUE(mock_pool_watcher.is_blacklisted()); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RegisterWatcherMissing) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, -ENOENT); + expect_timer_add_event(mock_threads); + + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(-ENOENT, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RegisterWatcherError) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, -EINVAL); + expect_timer_add_event(mock_threads); + + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RefreshBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, -EBLACKLISTED); + + MockListener mock_listener(this); + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + ASSERT_TRUE(mock_pool_watcher.is_blacklisted()); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RefreshMissing) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, -ENOENT); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RefreshError) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, -EINVAL); + expect_timer_add_event(mock_threads); + + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false); + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, GetMirrorUuidBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", -EBLACKLISTED); + + MockListener mock_listener(this); + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(-EBLACKLISTED, ctx.wait()); + ASSERT_TRUE(mock_pool_watcher.is_blacklisted()); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, GetMirrorUuidMissing) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "", -ENOENT); + expect_timer_add_event(mock_threads); + + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false); + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(-ENOENT, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, GetMirrorUuidError) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", -EINVAL); + expect_timer_add_event(mock_threads); + + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false); + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, Rewatch) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + expect_timer_add_event(mock_threads); + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false); + expect_refresh_images(mock_refresh_images_request, {{"global id", "image id"}}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + expect_listener_handle_update(mock_listener, "remote uuid", + {{"global id", "image id"}}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(wait_for_update(1)); + + MirroringWatcher::get_instance().handle_rewatch_complete(0); + ASSERT_TRUE(wait_for_update(1)); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RewatchBlacklist) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(wait_for_update(1)); + + MirroringWatcher::get_instance().handle_rewatch_complete(-EBLACKLISTED); + ASSERT_TRUE(mock_pool_watcher.is_blacklisted()); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, RewatchError) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + expect_timer_add_event(mock_threads); + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false); + expect_refresh_images(mock_refresh_images_request, {{"global id", "image id"}}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + expect_listener_handle_update(mock_listener, "remote uuid", + {{"global id", "image id"}}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(wait_for_update(1)); + + MirroringWatcher::get_instance().handle_rewatch_complete(-EINVAL); + ASSERT_TRUE(wait_for_update(1)); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, DeferredRefresh) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + MockRefreshImagesRequest mock_refresh_images_request; + + EXPECT_CALL(mock_refresh_images_request, send()) + .WillOnce(Invoke([&mock_refresh_images_request]() { + *mock_refresh_images_request.image_ids = {}; + MirroringWatcher::get_instance().handle_rewatch_complete(0); + mock_refresh_images_request.on_finish->complete(0); + })); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + expect_timer_add_event(mock_threads); + + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false); + expect_refresh_images(mock_refresh_images_request, {}, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", {}, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + ASSERT_TRUE(wait_for_update(1)); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +TEST_F(TestMockPoolWatcher, MirrorUuidUpdated) { + MockThreads mock_threads(m_threads); + expect_work_queue(mock_threads); + + InSequence seq; + MockMirroringWatcher mock_mirroring_watcher; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, true); + expect_mirroring_watcher_register(mock_mirroring_watcher, 0); + + ImageIds image_ids{ + {"global id 1", "remote id 1"}, + {"global id 2", "remote id 2"}}; + MockRefreshImagesRequest mock_refresh_images_request; + expect_refresh_images(mock_refresh_images_request, image_ids, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "remote uuid", 0); + + MockListener mock_listener(this); + expect_listener_handle_update(mock_listener, "remote uuid", image_ids, {}); + + MockPoolWatcher mock_pool_watcher(&mock_threads, m_remote_io_ctx, + mock_listener); + C_SaferCond ctx; + mock_pool_watcher.init(&ctx); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_TRUE(wait_for_update(1)); + + expect_timer_add_event(mock_threads); + ImageIds new_image_ids{ + {"global id 1", "remote id 1"}}; + expect_mirroring_watcher_is_unregistered(mock_mirroring_watcher, false); + expect_refresh_images(mock_refresh_images_request, new_image_ids, 0); + expect_mirror_uuid_get(m_remote_io_ctx, "updated uuid", 0); + expect_listener_handle_update(mock_listener, "remote uuid", {}, image_ids); + expect_listener_handle_update(mock_listener, "updated uuid", new_image_ids, + {}); + + MirroringWatcher::get_instance().handle_rewatch_complete(0); + ASSERT_TRUE(wait_for_update(2)); + + expect_mirroring_watcher_unregister(mock_mirroring_watcher, 0); + ASSERT_EQ(0, when_shut_down(mock_pool_watcher)); +} + +} // namespace mirror +} // namespace rbd diff --git a/src/test/rbd_mirror/test_mock_fixture.cc b/src/test/rbd_mirror/test_mock_fixture.cc new file mode 100644 index 00000000..9e308a63 --- /dev/null +++ b/src/test/rbd_mirror/test_mock_fixture.cc @@ -0,0 +1,64 @@ +// -*- 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 "test/librados_test_stub/LibradosTestStub.h" +#include "test/librados_test_stub/MockTestMemCluster.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "test/librbd/mock/MockImageCtx.h" + +namespace rbd { +namespace mirror { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::WithArg; + +TestMockFixture::TestClusterRef TestMockFixture::s_test_cluster; + +void TestMockFixture::SetUpTestCase() { + s_test_cluster = librados_test_stub::get_cluster(); + + // use a mock version of the in-memory rados client + librados_test_stub::set_cluster(boost::shared_ptr( + new ::testing::NiceMock())); + TestFixture::SetUpTestCase(); +} + +void TestMockFixture::TearDownTestCase() { + TestFixture::TearDownTestCase(); + librados_test_stub::set_cluster(s_test_cluster); +} + +void TestMockFixture::TearDown() { + // Mock rados client lives across tests -- reset it to initial state + librados::MockTestMemRadosClient *mock_rados_client = + get_mock_io_ctx(m_local_io_ctx).get_mock_rados_client(); + ASSERT_TRUE(mock_rados_client != nullptr); + + ::testing::Mock::VerifyAndClear(mock_rados_client); + mock_rados_client->default_to_dispatch(); + dynamic_cast( + librados_test_stub::get_cluster().get())->default_to_dispatch(); + + TestFixture::TearDown(); +} + +void TestMockFixture::expect_test_features(librbd::MockImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); +} + +librados::MockTestMemCluster& TestMockFixture::get_mock_cluster() { + librados::MockTestMemCluster* mock_cluster = dynamic_cast< + librados::MockTestMemCluster*>(librados_test_stub::get_cluster().get()); + ceph_assert(mock_cluster != nullptr); + return *mock_cluster; +} + +} // namespace mirror +} // namespace rbd + diff --git a/src/test/rbd_mirror/test_mock_fixture.h b/src/test/rbd_mirror/test_mock_fixture.h new file mode 100644 index 00000000..1f7dfb03 --- /dev/null +++ b/src/test/rbd_mirror/test_mock_fixture.h @@ -0,0 +1,71 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_RBD_MIRROR_TEST_MOCK_FIXTURE_H +#define CEPH_TEST_RBD_MIRROR_TEST_MOCK_FIXTURE_H + +#include "test/rbd_mirror/test_fixture.h" +#include "test/librados_test_stub/LibradosTestStub.h" +#include "common/WorkQueue.h" +#include +#include +#include "include/ceph_assert.h" + +namespace librados { +class TestRadosClient; +class MockTestMemCluster; +class MockTestMemIoCtxImpl; +class MockTestMemRadosClient; +} + +namespace librbd { +class MockImageCtx; +} + +ACTION_P(CopyInBufferlist, str) { + arg0->append(str); +} + +ACTION_P(CompleteContext, r) { + arg0->complete(r); +} + +ACTION_P2(CompleteContext, wq, r) { + ContextWQ *context_wq = reinterpret_cast(wq); + context_wq->queue(arg0, r); +} + +ACTION_P(GetReference, ref_object) { + ref_object->get(); +} + +MATCHER_P(ContentsEqual, bl, "") { + // TODO fix const-correctness of bufferlist + return const_cast(arg).contents_equal( + const_cast(bl)); +} + +namespace rbd { +namespace mirror { + +class TestMockFixture : public TestFixture { +public: + typedef boost::shared_ptr TestClusterRef; + + static void SetUpTestCase(); + static void TearDownTestCase(); + + void TearDown() override; + + void expect_test_features(librbd::MockImageCtx &mock_image_ctx); + + librados::MockTestMemCluster& get_mock_cluster(); + +private: + static TestClusterRef s_test_cluster; +}; + +} // namespace mirror +} // namespace rbd + +#endif // CEPH_TEST_RBD_MIRROR_TEST_MOCK_FIXTURE_H -- cgit v1.2.3