summaryrefslogtreecommitdiffstats
path: root/src/test/librbd/mirror/snapshot
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/test/librbd/mirror/snapshot
parentInitial commit. (diff)
downloadceph-upstream/16.2.11+ds.tar.xz
ceph-upstream/16.2.11+ds.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/librbd/mirror/snapshot')
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc388
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc404
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc159
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc389
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc385
-rw-r--r--src/test/librbd/mirror/snapshot/test_mock_Utils.cc177
6 files changed, 1902 insertions, 0 deletions
diff --git a/src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
new file mode 100644
index 000000000..d7e70cab7
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
@@ -0,0 +1,388 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockOperations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+#include "librbd/mirror/snapshot/WriteImageStateRequest.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD1(can_create_non_primary_snapshot,
+ bool(librbd::MockTestImageCtx *));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> bool can_create_non_primary_snapshot(
+ librbd::MockTestImageCtx *image_ctx) {
+ return Mock::s_instance->can_create_non_primary_snapshot(image_ctx);
+}
+
+} // namespace util
+
+template <>
+struct WriteImageStateRequest<MockTestImageCtx> {
+ uint64_t snap_id = CEPH_NOSNAP;
+ ImageState image_state;
+ Context* on_finish = nullptr;
+ static WriteImageStateRequest* s_instance;
+ static WriteImageStateRequest *create(MockTestImageCtx *image_ctx,
+ uint64_t snap_id,
+ const ImageState &image_state,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->snap_id = snap_id;
+ s_instance->image_state = image_state;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ WriteImageStateRequest() {
+ s_instance = this;
+ }
+};
+
+WriteImageStateRequest<MockTestImageCtx>* WriteImageStateRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.cc"
+template class librbd::mirror::snapshot::CreateNonPrimaryRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotCreateNonPrimaryRequest : public TestMockFixture {
+public:
+ typedef CreateNonPrimaryRequest<MockTestImageCtx> MockCreateNonPrimaryRequest;
+ typedef WriteImageStateRequest<MockTestImageCtx> MockWriteImageStateRequest;
+ typedef util::Mock MockUtils;
+
+ void expect_clone_md_ctx(MockTestImageCtx &mock_image_ctx) {
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), clone())
+ .WillOnce(Invoke([&mock_image_ctx]() {
+ get_mock_io_ctx(mock_image_ctx.md_ctx).get();
+ return &get_mock_io_ctx(mock_image_ctx.md_ctx);
+ }));
+ }
+
+ void expect_refresh_image(MockTestImageCtx &mock_image_ctx,
+ bool refresh_required, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+ .WillOnce(Return(refresh_required));
+ if (refresh_required) {
+ EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+ }
+
+ void expect_get_mirror_image(MockTestImageCtx &mock_image_ctx,
+ const cls::rbd::MirrorImage &mirror_image,
+ int r) {
+ using ceph::encode;
+ bufferlist bl;
+ encode(mirror_image, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_image_get"),
+ _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)),
+ Return(r)));
+ }
+
+ void expect_can_create_non_primary_snapshot(MockUtils &mock_utils,
+ bool result) {
+ EXPECT_CALL(mock_utils, can_create_non_primary_snapshot(_))
+ .WillOnce(Return(result));
+ }
+
+ void expect_get_mirror_peers(MockTestImageCtx &mock_image_ctx,
+ const std::vector<cls::rbd::MirrorPeer> &peers,
+ int r) {
+ using ceph::encode;
+ bufferlist bl;
+ encode(peers, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_peer_list"),
+ _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)),
+ Return(r)));
+ }
+
+ void expect_create_snapshot(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.operations, snap_create(_, _, _, _, _))
+ .WillOnce(WithArg<4>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+
+ void expect_write_image_state(
+ MockTestImageCtx &mock_image_ctx,
+ MockWriteImageStateRequest &mock_write_image_state_request, int r) {
+ EXPECT_CALL(mock_image_ctx, get_snap_id(_, _))
+ .WillOnce(Return(123));
+ EXPECT_CALL(mock_write_image_state_request, send())
+ .WillOnce(Invoke([&mock_image_ctx, &mock_write_image_state_request, r]() {
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_write_image_state_request.on_finish, r);
+ }));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockWriteImageStateRequest mock_write_image_state_request;
+ expect_write_image_state(mock_image_ctx, mock_write_image_state_request, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, SuccessDemoted) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockWriteImageStateRequest mock_write_image_state_request;
+ expect_write_image_state(mock_image_ctx, mock_write_image_state_request, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, true,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, RefreshError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, GetMirrorImageError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, false, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, CanNotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, false, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, false);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, GetMirrorPeersError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_get_mirror_peers(mock_image_ctx, {}, -EPERM);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, true,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EPERM, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, CreateSnapshotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_create_snapshot(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreateNonPrimaryRequest, WriteImageStateError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_refresh_image(mock_image_ctx, true, 0);
+ expect_get_mirror_image(
+ mock_image_ctx, {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "gid",
+ cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, 0);
+ MockUtils mock_utils;
+ expect_can_create_non_primary_snapshot(mock_utils, true);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockWriteImageStateRequest mock_write_image_state_request;
+ expect_write_image_state(mock_image_ctx, mock_write_image_state_request,
+ -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreateNonPrimaryRequest(&mock_image_ctx, false,
+ "mirror_uuid", 123, {{1, 2}}, {},
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
diff --git a/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc
new file mode 100644
index 000000000..bf0363486
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_CreatePrimaryRequest.cc
@@ -0,0 +1,404 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/stringify.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockOperations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/mirror/snapshot/CreatePrimaryRequest.h"
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD4(can_create_primary_snapshot,
+ bool(librbd::MockTestImageCtx *, bool, bool, uint64_t *));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx,
+ bool demoted, bool force,
+ bool* requires_orphan,
+ uint64_t *rollback_snap_id) {
+ return Mock::s_instance->can_create_primary_snapshot(image_ctx, demoted,
+ force, rollback_snap_id);
+}
+
+} // namespace util
+
+template <>
+struct UnlinkPeerRequest<MockTestImageCtx> {
+ uint64_t snap_id = CEPH_NOSNAP;
+ std::string mirror_peer_uuid;
+ Context* on_finish = nullptr;
+ static UnlinkPeerRequest* s_instance;
+ static UnlinkPeerRequest *create(MockTestImageCtx *image_ctx,
+ uint64_t snap_id,
+ const std::string &mirror_peer_uuid,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->snap_id = snap_id;
+ s_instance->mirror_peer_uuid = mirror_peer_uuid;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ UnlinkPeerRequest() {
+ s_instance = this;
+ }
+};
+
+UnlinkPeerRequest<MockTestImageCtx>* UnlinkPeerRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/CreatePrimaryRequest.cc"
+template class librbd::mirror::snapshot::CreatePrimaryRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotCreatePrimaryRequest : public TestMockFixture {
+public:
+ typedef CreatePrimaryRequest<MockTestImageCtx> MockCreatePrimaryRequest;
+ typedef UnlinkPeerRequest<MockTestImageCtx> MockUnlinkPeerRequest;
+ typedef util::Mock MockUtils;
+
+ uint64_t m_snap_seq = 0;
+
+ void snap_create(MockTestImageCtx &mock_image_ctx,
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name) {
+ ASSERT_TRUE(mock_image_ctx.snap_info.insert(
+ {m_snap_seq++,
+ SnapInfo{snap_name, ns, 0, {}, 0, 0, {}}}).second);
+ }
+
+ void expect_clone_md_ctx(MockTestImageCtx &mock_image_ctx) {
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), clone())
+ .WillOnce(Invoke([&mock_image_ctx]() {
+ get_mock_io_ctx(mock_image_ctx.md_ctx).get();
+ return &get_mock_io_ctx(mock_image_ctx.md_ctx);
+ }));
+ }
+
+ void expect_can_create_primary_snapshot(MockUtils &mock_utils, bool demoted,
+ bool force, bool result) {
+ EXPECT_CALL(mock_utils,
+ can_create_primary_snapshot(_, demoted, force, nullptr))
+ .WillOnce(Return(result));
+ }
+
+ void expect_get_mirror_peers(MockTestImageCtx &mock_image_ctx,
+ const std::vector<cls::rbd::MirrorPeer> &peers,
+ int r) {
+ using ceph::encode;
+ bufferlist bl;
+ encode(peers, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(RBD_MIRRORING, _, StrEq("rbd"), StrEq("mirror_peer_list"),
+ _, _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)),
+ Return(r)));
+ }
+
+ void expect_create_snapshot(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.operations, snap_create(_, _, _, _, _))
+ .WillOnce(DoAll(
+ Invoke([this, &mock_image_ctx, r](
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name,
+ uint64_t flags,
+ ProgressContext &prog_ctx,
+ Context *on_finish) {
+ if (r != 0) {
+ return;
+ }
+ snap_create(mock_image_ctx, ns, snap_name);
+ }),
+ WithArg<4>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue))
+ ));
+ }
+
+ void expect_unlink_peer(MockTestImageCtx &mock_image_ctx,
+ MockUnlinkPeerRequest &mock_unlink_peer_request,
+ uint64_t snap_id, const std::string &peer_uuid,
+ bool is_linked, int r) {
+ EXPECT_CALL(mock_unlink_peer_request, send())
+ .WillOnce(Invoke([&mock_image_ctx, &mock_unlink_peer_request,
+ snap_id, peer_uuid, is_linked, r]() {
+ ASSERT_EQ(mock_unlink_peer_request.mirror_peer_uuid,
+ peer_uuid);
+ ASSERT_EQ(mock_unlink_peer_request.snap_id, snap_id);
+ if (r == 0) {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ ASSERT_NE(it, mock_image_ctx.snap_info.end());
+ auto info =
+ boost::get<cls::rbd::MirrorSnapshotNamespace>(
+ &it->second.snap_namespace);
+ ASSERT_NE(nullptr, info);
+ ASSERT_EQ(is_linked, info->mirror_peer_uuids.erase(
+ peer_uuid));
+ if (info->mirror_peer_uuids.empty()) {
+ mock_image_ctx.snap_info.erase(it);
+ }
+ }
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_unlink_peer_request.on_finish, r);
+ }));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, CanNotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, false);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, GetMirrorPeersError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, CreateSnapshotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, SuccessUnlinkPeer) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->config.set_val("rbd_mirroring_max_mirroring_snapshots", "3");
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ for (int i = 0; i < 3; i++) {
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"uuid"}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, ns, "mirror_snap");
+ }
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ auto it = mock_image_ctx.snap_info.rbegin();
+ auto snap_id = it->first;
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid",
+ true, 0);
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, SuccessUnlinkNoPeer) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->config.set_val("rbd_mirroring_max_mirroring_snapshots", "3");
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ auto it = mock_image_ctx.snap_info.rbegin();
+ auto snap_id = it->first;
+ std::list<string> peer_uuids = {"uuid"};
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid",
+ false, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotCreatePrimaryRequest, SuccessUnlinkMultiplePeers) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->config.set_val("rbd_mirroring_max_mirroring_snapshots", "3");
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ for (int i = 0; i < 3; i++) {
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"uuid1", "uuid2"}, "",
+ CEPH_NOSNAP};
+ snap_create(mock_image_ctx, ns, "mirror_snap");
+ }
+
+ InSequence seq;
+
+ expect_clone_md_ctx(mock_image_ctx);
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, false, false, true);
+ expect_get_mirror_peers(mock_image_ctx,
+ {{"uuid1", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"},
+ {"uuid2", cls::rbd::MIRROR_PEER_DIRECTION_TX, "ceph",
+ "mirror", "mirror uuid"}}, 0);
+ expect_create_snapshot(mock_image_ctx, 0);
+ MockUnlinkPeerRequest mock_unlink_peer_request;
+ auto it = mock_image_ctx.snap_info.rbegin();
+ auto snap_id = it->first;
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid1",
+ true, 0);
+ expect_unlink_peer(mock_image_ctx, mock_unlink_peer_request, snap_id, "uuid2",
+ true, 0);
+ C_SaferCond ctx;
+ auto req = new MockCreatePrimaryRequest(&mock_image_ctx, "gid", CEPH_NOSNAP,
+ 0U, 0U, nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
diff --git a/src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc b/src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc
new file mode 100644
index 000000000..70a5eaeb0
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_ImageMeta.cc
@@ -0,0 +1,159 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "librbd/ImageState.h"
+#include "librbd/mirror/snapshot/ImageMeta.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/mirror/snapshot/ImageMeta.cc"
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotImageMeta : public TestMockFixture {
+public:
+ typedef ImageMeta<MockTestImageCtx> MockImageMeta;
+
+ void expect_metadata_get(MockTestImageCtx& mock_image_ctx,
+ const std::string& mirror_uuid,
+ const std::string& value, int r) {
+ bufferlist in_bl;
+ ceph::encode(util::get_image_meta_key(mirror_uuid), in_bl);
+
+ bufferlist out_bl;
+ ceph::encode(value, out_bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("metadata_get"), ContentsEqual(in_bl), _, _, _))
+ .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(out_bl)),
+ Return(r)));
+ }
+
+ void expect_metadata_set(MockTestImageCtx& mock_image_ctx,
+ const std::string& mirror_uuid,
+ const std::string& value, int r) {
+ bufferlist value_bl;
+ value_bl.append(value);
+
+ bufferlist in_bl;
+ ceph::encode(
+ std::map<std::string, bufferlist>{
+ {util::get_image_meta_key(mirror_uuid), value_bl}},
+ in_bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("metadata_set"), ContentsEqual(in_bl), _, _, _))
+ .WillOnce(Return(r));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotImageMeta, Load) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, LoadError) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", -EINVAL);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, LoadCorrupt) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_get(mock_image_ctx, "mirror uuid",
+ "\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ C_SaferCond ctx;
+ mock_image_meta.load(&ctx);
+ ASSERT_EQ(-EBADMSG, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, Save) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_set(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": true}", 0);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+ mock_image_meta.resync_requested = true;
+
+ C_SaferCond ctx;
+ mock_image_meta.save(&ctx);
+ ASSERT_EQ(0, ctx.wait());
+
+ // should have sent image-update notification
+ ASSERT_TRUE(image_ctx->state->is_refresh_required());
+}
+
+TEST_F(TestMockMirrorSnapshotImageMeta, SaveError) {
+ librbd::ImageCtx* image_ctx;
+ ASSERT_EQ(0, open_image(m_image_name, &image_ctx));
+ MockTestImageCtx mock_image_ctx(*image_ctx);
+
+ InSequence seq;
+ expect_metadata_set(mock_image_ctx, "mirror uuid",
+ "{\"resync_requested\": false}", -EINVAL);
+
+ MockImageMeta mock_image_meta(&mock_image_ctx, "mirror uuid");
+
+ C_SaferCond ctx;
+ mock_image_meta.save(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
diff --git a/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc
new file mode 100644
index 000000000..af9c34933
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_PromoteRequest.cc
@@ -0,0 +1,389 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/stringify.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockOperations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/image/ListWatchersRequest.h"
+#include "librbd/mirror/snapshot/CreateNonPrimaryRequest.h"
+#include "librbd/mirror/snapshot/CreatePrimaryRequest.h"
+#include "librbd/mirror/snapshot/PromoteRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+
+namespace image {
+
+template <>
+struct ListWatchersRequest<MockTestImageCtx> {
+ std::list<obj_watch_t> *watchers;
+ Context* on_finish = nullptr;
+ static ListWatchersRequest* s_instance;
+ static ListWatchersRequest *create(MockTestImageCtx &image_ctx, int flags,
+ std::list<obj_watch_t> *watchers,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->watchers = watchers;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ ListWatchersRequest() {
+ s_instance = this;
+ }
+};
+
+ListWatchersRequest<MockTestImageCtx>* ListWatchersRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace image
+
+namespace mirror {
+namespace snapshot {
+namespace util {
+
+namespace {
+
+struct Mock {
+ static Mock* s_instance;
+
+ Mock() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD5(can_create_primary_snapshot,
+ bool(librbd::MockTestImageCtx *, bool, bool, bool*, uint64_t *));
+};
+
+Mock *Mock::s_instance = nullptr;
+
+} // anonymous namespace
+
+template<> bool can_create_primary_snapshot(librbd::MockTestImageCtx *image_ctx,
+ bool demoted, bool force,
+ bool* requires_orphan,
+ uint64_t *rollback_snap_id) {
+ return Mock::s_instance->can_create_primary_snapshot(image_ctx, demoted,
+ force, requires_orphan,
+ rollback_snap_id);
+}
+
+} // namespace util
+
+template <>
+struct CreateNonPrimaryRequest<MockTestImageCtx> {
+ std::string primary_mirror_uuid;
+ uint64_t primary_snap_id = CEPH_NOSNAP;
+ Context* on_finish = nullptr;
+ static CreateNonPrimaryRequest* s_instance;
+ static CreateNonPrimaryRequest *create(MockTestImageCtx *image_ctx,
+ bool demoted,
+ const std::string &primary_mirror_uuid,
+ uint64_t primary_snap_id,
+ SnapSeqs snap_seqs,
+ const ImageState &image_state,
+ uint64_t *snap_id,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->primary_mirror_uuid = primary_mirror_uuid;
+ s_instance->primary_snap_id = primary_snap_id;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ CreateNonPrimaryRequest() {
+ s_instance = this;
+ }
+};
+
+CreateNonPrimaryRequest<MockTestImageCtx>* CreateNonPrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+template <>
+struct CreatePrimaryRequest<MockTestImageCtx> {
+ bool demoted = false;
+ bool force = false;
+ Context* on_finish = nullptr;
+ static CreatePrimaryRequest* s_instance;
+ static CreatePrimaryRequest *create(MockTestImageCtx *image_ctx,
+ const std::string& global_image_id,
+ uint64_t clean_since_snap_id,
+ uint64_t snap_create_flags,
+ uint32_t flags, uint64_t *snap_id,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->demoted = ((flags & CREATE_PRIMARY_FLAG_DEMOTED) != 0);
+ s_instance->force = ((flags & CREATE_PRIMARY_FLAG_FORCE) != 0);
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ MOCK_METHOD0(send, void());
+
+ CreatePrimaryRequest() {
+ s_instance = this;
+ }
+};
+
+CreatePrimaryRequest<MockTestImageCtx>* CreatePrimaryRequest<MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/PromoteRequest.cc"
+template class librbd::mirror::snapshot::PromoteRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+
+class TestMockMirrorSnapshotPromoteRequest : public TestMockFixture {
+public:
+ typedef librbd::image::ListWatchersRequest<MockTestImageCtx> MockListWatchersRequest;
+ typedef PromoteRequest<MockTestImageCtx> MockPromoteRequest;
+ typedef CreateNonPrimaryRequest<MockTestImageCtx> MockCreateNonPrimaryRequest;
+ typedef CreatePrimaryRequest<MockTestImageCtx> MockCreatePrimaryRequest;
+ typedef util::Mock MockUtils;
+
+ void expect_can_create_primary_snapshot(MockUtils &mock_utils, bool force,
+ bool requires_orphan,
+ uint64_t rollback_snap_id,
+ bool result) {
+ EXPECT_CALL(mock_utils,
+ can_create_primary_snapshot(_, false, force, _, _))
+ .WillOnce(DoAll(
+ WithArgs<3,4 >(Invoke(
+ [requires_orphan, rollback_snap_id]
+ (bool* orphan, uint64_t *snap_id) {
+ *orphan = requires_orphan;
+ *snap_id = rollback_snap_id;
+ })),
+ Return(result)));
+ }
+
+ void expect_create_orphan_snapshot(
+ MockTestImageCtx &mock_image_ctx,
+ MockCreateNonPrimaryRequest &mock_create_non_primary_request, int r) {
+ EXPECT_CALL(mock_create_non_primary_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_create_non_primary_request, r]() {
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_create_non_primary_request.on_finish, r);
+ }));
+ }
+
+ void expect_list_watchers(
+ MockTestImageCtx &mock_image_ctx,
+ MockListWatchersRequest &mock_list_watchers_request,
+ const std::list<obj_watch_t> &watchers, int r) {
+ EXPECT_CALL(mock_list_watchers_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_list_watchers_request, watchers, r]() {
+ *mock_list_watchers_request.watchers = watchers;
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_list_watchers_request.on_finish, r);
+ }));
+ }
+
+ void expect_acquire_lock(MockTestImageCtx &mock_image_ctx, int r) {
+ if (mock_image_ctx.exclusive_lock == nullptr) {
+ return;
+ }
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner())
+ .WillOnce(Return(false));
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, block_requests(_));
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, acquire_lock(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ if (r == 0) {
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner())
+ .WillOnce(Return(true));
+ }
+ }
+
+ void expect_get_snap_info(MockTestImageCtx &mock_image_ctx,
+ uint64_t snap_id, const SnapInfo* snap_info) {
+ EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id))
+ .WillOnce(Return(snap_info));
+ }
+
+ void expect_rollback(MockTestImageCtx &mock_image_ctx, uint64_t snap_id,
+ const SnapInfo* snap_info, int r) {
+ expect_get_snap_info(mock_image_ctx, snap_id, snap_info);
+ EXPECT_CALL(*mock_image_ctx.operations,
+ execute_snap_rollback(snap_info->snap_namespace,
+ snap_info->name, _, _))
+ .WillOnce(WithArg<3>(CompleteContext(
+ r, mock_image_ctx.image_ctx->op_work_queue)));
+ }
+
+ void expect_create_promote_snapshot(
+ MockTestImageCtx &mock_image_ctx,
+ MockCreatePrimaryRequest &mock_create_primary_request, int r) {
+ EXPECT_CALL(mock_create_primary_request, send())
+ .WillOnce(
+ Invoke([&mock_image_ctx, &mock_create_primary_request, r]() {
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ mock_create_primary_request.on_finish, r);
+ }));
+ }
+
+ void expect_release_lock(MockTestImageCtx &mock_image_ctx, int r) {
+ if (mock_image_ctx.exclusive_lock == nullptr) {
+ return;
+ }
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, unblock_requests());
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock, release_lock(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, false, CEPH_NOSNAP,
+ true);
+ MockCreatePrimaryRequest mock_create_primary_request;
+ expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+ 0);
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, SuccessForce) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ }
+
+ InSequence seq;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, true, CEPH_NOSNAP, true);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_orphan_snapshot(mock_image_ctx, mock_create_non_primary_request,
+ 0);
+ MockListWatchersRequest mock_list_watchers_request;
+ expect_list_watchers(mock_image_ctx, mock_list_watchers_request, {}, 0);
+ expect_acquire_lock(mock_image_ctx, 0);
+
+ SnapInfo snap_info = {"snap", cls::rbd::MirrorSnapshotNamespace{}, 0,
+ {}, 0, 0, {}};
+ MockCreatePrimaryRequest mock_create_primary_request;
+ expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+ 0);
+ expect_release_lock(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, SuccessRollback) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) {
+ mock_image_ctx.exclusive_lock = &mock_exclusive_lock;
+ }
+
+ InSequence seq;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, false, 123, true);
+ MockCreateNonPrimaryRequest mock_create_non_primary_request;
+ expect_create_orphan_snapshot(mock_image_ctx, mock_create_non_primary_request,
+ 0);
+ MockListWatchersRequest mock_list_watchers_request;
+ expect_list_watchers(mock_image_ctx, mock_list_watchers_request, {}, 0);
+ expect_acquire_lock(mock_image_ctx, 0);
+
+ SnapInfo snap_info = {"snap", cls::rbd::MirrorSnapshotNamespace{}, 0,
+ {}, 0, 0, {}};
+ expect_rollback(mock_image_ctx, 123, &snap_info, 0);
+ MockCreatePrimaryRequest mock_create_primary_request;
+ expect_create_promote_snapshot(mock_image_ctx, mock_create_primary_request,
+ 0);
+ expect_release_lock(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotPromoteRequest, ErrorCannotRollback) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+
+ MockUtils mock_utils;
+ expect_can_create_primary_snapshot(mock_utils, true, false, CEPH_NOSNAP,
+ false);
+
+ C_SaferCond ctx;
+ auto req = new MockPromoteRequest(&mock_image_ctx, "gid", &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+
diff --git a/src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc b/src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc
new file mode 100644
index 000000000..456b6ccdc
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_UnlinkPeerRequest.cc
@@ -0,0 +1,385 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockOperations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.cc"
+template class librbd::mirror::snapshot::UnlinkPeerRequest<librbd::MockTestImageCtx>;
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+
+class TestMockMirrorSnapshotUnlinkPeerRequest : public TestMockFixture {
+public:
+ typedef UnlinkPeerRequest<MockTestImageCtx> MockUnlinkPeerRequest;
+
+ uint64_t m_snap_seq = 0;
+
+ uint64_t snap_create(MockTestImageCtx &mock_image_ctx,
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name) {
+ EXPECT_TRUE(mock_image_ctx.snap_info.insert(
+ {++m_snap_seq,
+ SnapInfo{snap_name, ns, 0, {}, 0, 0, {}}}).second);
+ return m_snap_seq;
+ }
+
+ void expect_get_snap_info(MockTestImageCtx &mock_image_ctx,
+ librados::snap_t snap_id) {
+ EXPECT_CALL(mock_image_ctx, get_snap_info(snap_id))
+ .WillRepeatedly(Invoke([&mock_image_ctx](
+ librados::snap_t snap_id) -> librbd::SnapInfo * {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ if (it == mock_image_ctx.snap_info.end()) {
+ return nullptr;
+ }
+ return &it->second;
+ }));
+ }
+
+ void expect_is_refresh_required(MockTestImageCtx &mock_image_ctx,
+ bool refresh_required) {
+ EXPECT_CALL(*mock_image_ctx.state, is_refresh_required())
+ .WillOnce(Return(refresh_required));
+ }
+
+ void expect_refresh_image(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.state, refresh(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ void expect_unlink_peer(MockTestImageCtx &mock_image_ctx, uint64_t snap_id,
+ const std::string &peer_uuid, int r) {
+ using ceph::encode;
+ bufferlist bl;
+ encode(snapid_t{snap_id}, bl);
+ encode(peer_uuid, bl);
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx),
+ exec(mock_image_ctx.header_oid, _, StrEq("rbd"),
+ StrEq("mirror_image_snapshot_unlink_peer"),
+ ContentsEqual(bl), _, _, _))
+ .WillOnce(Invoke([&mock_image_ctx, snap_id, peer_uuid, r](auto&&... args) -> int {
+ if (r == 0) {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ EXPECT_NE(it, mock_image_ctx.snap_info.end());
+ auto info =
+ boost::get<cls::rbd::MirrorSnapshotNamespace>(
+ &it->second.snap_namespace);
+ EXPECT_NE(nullptr, info);
+ EXPECT_NE(0, info->mirror_peer_uuids.erase(
+ peer_uuid));
+ }
+ return r;
+ }));
+ }
+
+ void expect_notify_update(MockTestImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(mock_image_ctx, notify_update(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ void expect_remove_snapshot(MockTestImageCtx &mock_image_ctx,
+ uint64_t snap_id, int r) {
+ EXPECT_CALL(*mock_image_ctx.operations, snap_remove(_, _, _))
+ .WillOnce(Invoke([&mock_image_ctx, snap_id, r](
+ const cls::rbd::SnapshotNamespace &snap_namespace,
+ const std::string &snap_name, Context *on_finish) {
+ if (r == 0) {
+ auto it = mock_image_ctx.snap_info.find(snap_id);
+ EXPECT_NE(it, mock_image_ctx.snap_info.end());
+ EXPECT_EQ(it->second.snap_namespace, snap_namespace);
+ EXPECT_EQ(it->second.name, snap_name);
+ mock_image_ctx.snap_info.erase(it);
+ }
+ mock_image_ctx.image_ctx->op_work_queue->queue(
+ on_finish, r);
+ }));
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, Success) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer1_uuid", "peer2_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+ expect_unlink_peer(mock_image_ctx, snap_id, "peer1_uuid", 0);
+ expect_notify_update(mock_image_ctx, 0);
+ expect_refresh_image(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer1_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, RemoveSnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+ snap_create(mock_image_ctx, ns, "mirror_snap2");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+ expect_remove_snapshot(mock_image_ctx, snap_id, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, SnapshotRemoveEmptyPeers) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+ ns.mirror_peer_uuids = {"peer_uuid"};
+ snap_create(mock_image_ctx, ns, "mirror_snap2");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+ expect_remove_snapshot(mock_image_ctx, snap_id, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, SnapshotDNE) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ expect_get_snap_info(mock_image_ctx, 123);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, 123, "peer_uuid", &ctx);
+ req->send();
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, PeerDNE) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, 0);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "unknown_peer",
+ &ctx);
+ req->send();
+ ASSERT_EQ(0, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, InvalidSnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::UserSnapshotNamespace ns;
+ auto snap_id = snap_create(mock_image_ctx, ns, "user_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, RefreshError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, true);
+ expect_refresh_image(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, 123, "peer_uuid", &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, UnlinkError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer1_uuid", "peer2_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+ expect_unlink_peer(mock_image_ctx, snap_id, "peer1_uuid", -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer1_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, NotifyError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer1_uuid", "peer2_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+ expect_unlink_peer(mock_image_ctx, snap_id, "peer1_uuid", 0);
+ expect_notify_update(mock_image_ctx, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer1_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMirrorSnapshotUnlinkPeerRequest, RemoveSnapshotError) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+ cls::rbd::MirrorSnapshotNamespace ns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"peer_uuid"},
+ "", CEPH_NOSNAP};
+ auto snap_id = snap_create(mock_image_ctx, ns, "mirror_snap");
+ snap_create(mock_image_ctx, ns, "mirror_snap2");
+
+ expect_get_snap_info(mock_image_ctx, snap_id);
+
+ InSequence seq;
+
+ expect_is_refresh_required(mock_image_ctx, false);
+ expect_remove_snapshot(mock_image_ctx, snap_id, -EINVAL);
+
+ C_SaferCond ctx;
+ auto req = new MockUnlinkPeerRequest(&mock_image_ctx, snap_id, "peer_uuid",
+ &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
diff --git a/src/test/librbd/mirror/snapshot/test_mock_Utils.cc b/src/test/librbd/mirror/snapshot/test_mock_Utils.cc
new file mode 100644
index 000000000..410dc3eba
--- /dev/null
+++ b/src/test/librbd/mirror/snapshot/test_mock_Utils.cc
@@ -0,0 +1,177 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/stringify.h"
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockOperations.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "librbd/mirror/snapshot/UnlinkPeerRequest.h"
+#include "librbd/mirror/snapshot/Utils.h"
+
+namespace librbd {
+
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+// template definitions
+#include "librbd/mirror/snapshot/Utils.cc"
+template bool librbd::mirror::snapshot::util::can_create_primary_snapshot(
+ librbd::MockTestImageCtx *image_ctx, bool demoted, bool force,
+ bool* requires_orphan, uint64_t *rollback_snap_id);
+template bool librbd::mirror::snapshot::util::can_create_non_primary_snapshot(
+ librbd::MockTestImageCtx *image_ctx);
+
+namespace librbd {
+namespace mirror {
+namespace snapshot {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+
+class TestMockMirrorSnapshotUtils : public TestMockFixture {
+public:
+ uint64_t m_snap_seq = 0;
+
+ uint64_t snap_create(MockTestImageCtx &mock_image_ctx,
+ const cls::rbd::SnapshotNamespace &ns,
+ const std::string& snap_name) {
+ EXPECT_TRUE(mock_image_ctx.snap_info.insert(
+ {++m_snap_seq,
+ SnapInfo{snap_name, ns, 0, {}, 0, 0, {}}}).second);
+ return m_snap_seq;
+ }
+};
+
+TEST_F(TestMockMirrorSnapshotUtils, CanCreatePrimarySnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ // no previous mirror snapshots found
+ bool requires_orphan;
+ uint64_t rollback_snap_id;
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ &requires_orphan,
+ &rollback_snap_id));
+ ASSERT_FALSE(requires_orphan);
+ ASSERT_EQ(rollback_snap_id, CEPH_NOSNAP);
+
+ cls::rbd::MirrorSnapshotNamespace nns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "mirror_uuid", 123};
+ nns.complete = true;
+ auto copied_snap_id = snap_create(mock_image_ctx, nns, "NPS1");
+
+ // without force, previous snapshot is non-primary
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ nullptr, nullptr));
+
+ // demoted, previous snapshot is non-primary
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, true, true,
+ nullptr, nullptr));
+
+ // previous non-primary snapshot is copied
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ &requires_orphan,
+ &rollback_snap_id));
+ ASSERT_TRUE(requires_orphan);
+ ASSERT_EQ(rollback_snap_id, CEPH_NOSNAP);
+
+ nns.complete = false;
+ snap_create(mock_image_ctx, nns, "NPS2");
+
+ // previous non-primary snapshot is not copied yet
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, nullptr));
+
+ // can rollback
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, &rollback_snap_id));
+ ASSERT_EQ(rollback_snap_id, copied_snap_id);
+
+ nns.state = cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED;
+ snap_create(mock_image_ctx, nns, "NPS3");
+
+ // previous non-primary snapshot is orphan
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, nullptr));
+
+ cls::rbd::MirrorSnapshotNamespace pns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY_DEMOTED, {"uuid"}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, pns, "PS1");
+
+ // previous primary snapshot is demoted, no force
+ ASSERT_FALSE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ nullptr, nullptr));
+
+ // previous primary snapshot is demoted, force
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, true,
+ nullptr, nullptr));
+
+ pns.state = cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY;
+ snap_create(mock_image_ctx, pns, "PS2");
+
+ // previous snapshot is not demoted primary
+ ASSERT_TRUE(util::can_create_primary_snapshot(&mock_image_ctx, false, false,
+ nullptr, nullptr));
+}
+
+TEST_F(TestMockMirrorSnapshotUtils, CanCreateNonPrimarySnapshot) {
+ REQUIRE_FORMAT_V2();
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ // no previous mirror snapshots found
+ ASSERT_TRUE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ cls::rbd::MirrorSnapshotNamespace nns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY, {}, "mirror_uuid", 123};
+ snap_create(mock_image_ctx, nns, "NPS1");
+
+ // previous non-primary snapshot is not copied yet
+ ASSERT_FALSE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ nns.complete = true;
+ snap_create(mock_image_ctx, nns, "NPS2");
+
+ // previous non-primary snapshot is copied
+ ASSERT_TRUE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ cls::rbd::MirrorSnapshotNamespace pns{
+ cls::rbd::MIRROR_SNAPSHOT_STATE_PRIMARY, {"uuid"}, "", CEPH_NOSNAP};
+ snap_create(mock_image_ctx, pns, "PS1");
+
+ // previous primary snapshot is not in demoted state
+ ASSERT_FALSE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+
+ pns.state = cls::rbd::MIRROR_SNAPSHOT_STATE_NON_PRIMARY_DEMOTED;
+ snap_create(mock_image_ctx, pns, "PS2");
+
+ // previous primary snapshot is in demoted state
+ ASSERT_TRUE(util::can_create_non_primary_snapshot(&mock_image_ctx));
+}
+
+} // namespace snapshot
+} // namespace mirror
+} // namespace librbd
+