--- /dev/null
+// -*- 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/rbd/librbd.hpp"
+#include "journal/Journaler.h"
+#include "librbd/AioImageRequestWQ.h"
+#include "librbd/ExclusiveLock.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/ImageState.h"
+#include "tools/rbd_mirror/ImageSync.h"
+#include "tools/rbd_mirror/Threads.h"
+
+void register_test_image_sync() {
+}
+
+namespace rbd {
+namespace mirror {
+
+namespace {
+
+void scribble(librbd::ImageCtx *image_ctx, int num_ops, size_t max_size)
+{
+ for (int i=0; i<num_ops; i++) {
+ uint64_t off = rand() % (image_ctx->size - max_size + 1);
+ uint64_t len = 1 + rand() % max_size;
+
+ if (rand() % 4 == 0) {
+ ASSERT_EQ((int)len, image_ctx->aio_work_queue->discard(off, len));
+ } else {
+ std::string str(len, '1');
+ ASSERT_EQ((int)len, image_ctx->aio_work_queue->write(off, len,
+ str.c_str(), 0));
+ }
+ }
+}
+
+} // anonymous namespace
+class TestImageSync : public TestFixture {
+public:
+
+ virtual void SetUp() {
+ 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_threads = new rbd::mirror::Threads(reinterpret_cast<CephContext*>(
+ m_local_io_ctx.cct()));
+
+ 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", 5);
+
+ 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));
+ }
+
+ virtual void TearDown() {
+ delete m_threads;
+ TestFixture::TearDown();
+ }
+
+ 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_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,
+ ctx);
+ }
+
+ librbd::ImageCtx *m_remote_image_ctx;
+ librbd::ImageCtx *m_local_image_ctx;
+ ::journal::Journaler *m_remote_journaler;
+ librbd::journal::MirrorPeerClientMeta m_client_meta;
+
+ rbd::mirror::Threads *m_threads = nullptr;
+};
+
+TEST_F(TestImageSync, Empty) {
+ C_SaferCond ctx;
+ ImageSync<> *request = create_request(&ctx);
+ request->start();
+ 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);
+ {
+ RWLock::RLocker owner_locker(m_remote_image_ctx->owner_lock);
+ ASSERT_EQ(0, m_remote_image_ctx->flush());
+ }
+
+ C_SaferCond ctx;
+ ImageSync<> *request = create_request(&ctx);
+ request->start();
+ ASSERT_EQ(0, ctx.wait());
+
+ int64_t object_size = std::min<int64_t>(
+ 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->aio_work_queue->read(
+ offset, object_size, read_remote_bl.c_str(), 0));
+ ASSERT_LE(0, m_local_image_ctx->aio_work_queue->read(
+ offset, object_size, read_local_bl.c_str(), 0));
+ ASSERT_TRUE(read_remote_bl.contents_equal(read_local_bl));
+ }
+}
+
+} // namespace mirror
+} // namespace rbd
--- /dev/null
+// -*- 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 "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/rbd_mirror/mock/MockJournaler.h"
+#include "tools/rbd_mirror/ImageSync.h"
+#include "tools/rbd_mirror/Threads.h"
+#include "tools/rbd_mirror/image_sync/ImageCopyRequest.h"
+#include "tools/rbd_mirror/image_sync/SnapshotCopyRequest.h"
+#include "tools/rbd_mirror/image_sync/SyncPointCreateRequest.h"
+#include "tools/rbd_mirror/image_sync/SyncPointPruneRequest.h"
+
+// template definitions
+#include "tools/rbd_mirror/ImageSync.cc"
+template class rbd::mirror::ImageSync<librbd::MockImageCtx>;
+
+namespace rbd {
+namespace mirror {
+
+namespace image_sync {
+
+template <>
+class ImageCopyRequest<librbd::MockImageCtx> {
+public:
+ static ImageCopyRequest* s_instance;
+ Context *on_finish;
+
+ static ImageCopyRequest* create(librbd::MockImageCtx *local_image_ctx,
+ librbd::MockImageCtx *remote_image_ctx,
+ SafeTimer *timer, Mutex *timer_lock,
+ journal::MockJournaler *journaler,
+ librbd::journal::MirrorPeerClientMeta *client_meta,
+ librbd::journal::MirrorPeerSyncPoint *sync_point,
+ Context *on_finish) {
+ assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ ImageCopyRequest() {
+ s_instance = this;
+ }
+ MOCK_METHOD0(cancel, void());
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+class SnapshotCopyRequest<librbd::MockImageCtx> {
+public:
+ static SnapshotCopyRequest* s_instance;
+ Context *on_finish;
+
+ static SnapshotCopyRequest* create(librbd::MockImageCtx *local_image_ctx,
+ librbd::MockImageCtx *remote_image_ctx,
+ SnapshotCopyRequest<librbd::ImageCtx>::SnapMap *snap_map,
+ journal::MockJournaler *journaler,
+ librbd::journal::MirrorPeerClientMeta *client_meta,
+ Context *on_finish) {
+ assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ SnapshotCopyRequest() {
+ s_instance = this;
+ }
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+class SyncPointCreateRequest<librbd::MockImageCtx> {
+public:
+ static SyncPointCreateRequest *s_instance;
+ Context *on_finish;
+
+ static SyncPointCreateRequest* create(librbd::MockImageCtx *remote_image_ctx,
+ const std::string &mirror_uuid,
+ journal::MockJournaler *journaler,
+ librbd::journal::MirrorPeerClientMeta *client_meta,
+ Context *on_finish) {
+ assert(s_instance != nullptr);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ SyncPointCreateRequest() {
+ s_instance = this;
+ }
+ MOCK_METHOD0(send, void());
+};
+
+template <>
+class SyncPointPruneRequest<librbd::MockImageCtx> {
+public:
+ static SyncPointPruneRequest *s_instance;
+ Context *on_finish;
+ bool sync_complete;
+
+ static SyncPointPruneRequest* create(librbd::MockImageCtx *remote_image_ctx,
+ bool sync_complete,
+ journal::MockJournaler *journaler,
+ librbd::journal::MirrorPeerClientMeta *client_meta,
+ Context *on_finish) {
+ 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());
+};
+
+ImageCopyRequest<librbd::MockImageCtx>* ImageCopyRequest<librbd::MockImageCtx>::s_instance = nullptr;
+SnapshotCopyRequest<librbd::MockImageCtx>* SnapshotCopyRequest<librbd::MockImageCtx>::s_instance = nullptr;
+SyncPointCreateRequest<librbd::MockImageCtx>* SyncPointCreateRequest<librbd::MockImageCtx>::s_instance = nullptr;
+SyncPointPruneRequest<librbd::MockImageCtx>* SyncPointPruneRequest<librbd::MockImageCtx>::s_instance = nullptr;
+
+} // namespace image_sync
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+
+class TestMockImageSync : public TestMockFixture {
+public:
+ typedef ImageSync<librbd::MockImageCtx> MockImageSync;
+ typedef image_sync::ImageCopyRequest<librbd::MockImageCtx> MockImageCopyRequest;
+ typedef image_sync::SnapshotCopyRequest<librbd::MockImageCtx> MockSnapshotCopyRequest;
+ typedef image_sync::SyncPointCreateRequest<librbd::MockImageCtx> MockSyncPointCreateRequest;
+ typedef image_sync::SyncPointPruneRequest<librbd::MockImageCtx> MockSyncPointPruneRequest;
+
+ virtual void SetUp() {
+ 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));
+
+ m_threads = new rbd::mirror::Threads(reinterpret_cast<CephContext*>(
+ m_local_io_ctx.cct()));
+ }
+
+ virtual void TearDown() {
+ delete m_threads;
+ TestMockFixture::TearDown();
+ }
+
+ void expect_create_sync_point(MockSyncPointCreateRequest &mock_sync_point_create_request,
+ int r) {
+ EXPECT_CALL(mock_sync_point_create_request, send())
+ .WillOnce(Invoke([this, &mock_sync_point_create_request, r]() {
+ if (r == 0) {
+ m_client_meta.sync_points.emplace_back("snap1", boost::none);
+ }
+ m_threads->work_queue->queue(mock_sync_point_create_request.on_finish, r);
+ }));
+ }
+
+ void expect_copy_snapshots(MockSnapshotCopyRequest &mock_snapshot_copy_request, int r) {
+ EXPECT_CALL(mock_snapshot_copy_request, send())
+ .WillOnce(Invoke([this, &mock_snapshot_copy_request, r]() {
+ m_threads->work_queue->queue(mock_snapshot_copy_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_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::MockImageCtx &mock_remote_image_ctx,
+ librbd::MockImageCtx &mock_local_image_ctx,
+ journal::MockJournaler &mock_journaler,
+ 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,
+ ctx);
+ }
+
+ librbd::ImageCtx *m_remote_image_ctx;
+ librbd::ImageCtx *m_local_image_ctx;
+ librbd::journal::MirrorPeerClientMeta m_client_meta;
+
+ rbd::mirror::Threads *m_threads = nullptr;
+};
+
+TEST_F(TestMockImageSync, SimpleSync) {
+ librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ journal::MockJournaler mock_journaler;
+ MockImageCopyRequest mock_image_copy_request;
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ MockSyncPointCreateRequest mock_sync_point_create_request;
+ MockSyncPointPruneRequest mock_sync_point_prune_request;
+
+ InSequence seq;
+ expect_create_sync_point(mock_sync_point_create_request, 0);
+ expect_copy_snapshots(mock_snapshot_copy_request, 0);
+ expect_copy_image(mock_image_copy_request, 0);
+ expect_prune_sync_point(mock_sync_point_prune_request, true, 0);
+
+ C_SaferCond ctx;
+ MockImageSync *request = create_request(mock_remote_image_ctx,
+ mock_local_image_ctx,
+ mock_journaler, &ctx);
+ request->start();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSync, RestartSync) {
+ librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ journal::MockJournaler mock_journaler;
+ MockImageCopyRequest mock_image_copy_request;
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ MockSyncPointCreateRequest mock_sync_point_create_request;
+ MockSyncPointPruneRequest mock_sync_point_prune_request;
+
+ m_client_meta.sync_points = {{"snap1", boost::none},
+ {"snap2", "snap1", boost::none}};
+
+ InSequence seq;
+ expect_prune_sync_point(mock_sync_point_prune_request, false, 0);
+ expect_copy_snapshots(mock_snapshot_copy_request, 0);
+ expect_copy_image(mock_image_copy_request, 0);
+ expect_prune_sync_point(mock_sync_point_prune_request, true, 0);
+
+ C_SaferCond ctx;
+ MockImageSync *request = create_request(mock_remote_image_ctx,
+ mock_local_image_ctx,
+ mock_journaler, &ctx);
+ request->start();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockImageSync, CancelImageCopy) {
+ librbd::MockImageCtx mock_remote_image_ctx(*m_remote_image_ctx);
+ librbd::MockImageCtx mock_local_image_ctx(*m_local_image_ctx);
+ journal::MockJournaler mock_journaler;
+ MockImageCopyRequest mock_image_copy_request;
+ MockSnapshotCopyRequest mock_snapshot_copy_request;
+ MockSyncPointCreateRequest mock_sync_point_create_request;
+ MockSyncPointPruneRequest mock_sync_point_prune_request;
+
+ m_client_meta.sync_points = {{"snap1", boost::none}};
+
+ InSequence seq;
+ expect_copy_snapshots(mock_snapshot_copy_request, 0);
+
+ C_SaferCond image_copy_ctx;
+ EXPECT_CALL(mock_image_copy_request, send())
+ .WillOnce(Invoke([&image_copy_ctx]() {
+ image_copy_ctx.complete(0);
+ }));
+ EXPECT_CALL(mock_image_copy_request, cancel());
+
+ C_SaferCond ctx;
+ MockImageSync *request = create_request(mock_remote_image_ctx,
+ mock_local_image_ctx,
+ mock_journaler, &ctx);
+ request->start();
+
+ // cancel the image copy once it starts
+ ASSERT_EQ(0, image_copy_ctx.wait());
+ request->cancel();
+ m_threads->work_queue->queue(mock_image_copy_request.on_finish, 0);
+
+ ASSERT_EQ(-ECANCELED, ctx.wait());
+}
+
+} // namespace mirror
+} // namespace rbd