diff options
Diffstat (limited to 'src/test/librbd/mirror')
7 files changed, 2596 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 + diff --git a/src/test/librbd/mirror/test_mock_DisableRequest.cc b/src/test/librbd/mirror/test_mock_DisableRequest.cc new file mode 100644 index 000000000..823884fe5 --- /dev/null +++ b/src/test/librbd/mirror/test_mock_DisableRequest.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 "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockImageState.h" +#include "test/librbd/mock/MockOperations.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "librbd/journal/PromoteRequest.h" +#include "librbd/mirror/DisableRequest.h" +#include "librbd/mirror/GetInfoRequest.h" +#include "librbd/mirror/ImageRemoveRequest.h" +#include "librbd/mirror/ImageStateUpdateRequest.h" +#include "librbd/mirror/snapshot/PromoteRequest.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + explicit MockTestImageCtx(librbd::ImageCtx& image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace journal { + +template <> +struct PromoteRequest<librbd::MockTestImageCtx> { + Context *on_finish = nullptr; + static PromoteRequest *s_instance; + static PromoteRequest *create(librbd::MockTestImageCtx *, bool force, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + PromoteRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +PromoteRequest<librbd::MockTestImageCtx> *PromoteRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace journal + +namespace mirror { +template <> +struct GetInfoRequest<librbd::MockTestImageCtx> { + cls::rbd::MirrorImage *mirror_image; + PromotionState *promotion_state; + Context *on_finish = nullptr; + static GetInfoRequest *s_instance; + static GetInfoRequest *create(librbd::MockTestImageCtx &, + cls::rbd::MirrorImage *mirror_image, + PromotionState *promotion_state, + std::string* primary_mirror_uuid, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->mirror_image = mirror_image; + s_instance->promotion_state = promotion_state; + s_instance->on_finish = on_finish; + return s_instance; + } + + GetInfoRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template <> +struct ImageRemoveRequest<librbd::MockTestImageCtx> { + static ImageRemoveRequest* s_instance; + Context* on_finish = nullptr; + + static ImageRemoveRequest* create( + librados::IoCtx& io_ctx, + const std::string& global_image_id, + const std::string& image_id, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + ImageRemoveRequest() { + s_instance = this; + } +}; + +template <> +struct ImageStateUpdateRequest<librbd::MockTestImageCtx> { + static ImageStateUpdateRequest* s_instance; + Context* on_finish = nullptr; + + static ImageStateUpdateRequest* create( + librados::IoCtx& io_ctx, + const std::string& image_id, + cls::rbd::MirrorImageState mirror_image_state, + const cls::rbd::MirrorImage& mirror_image, + Context* on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + ImageStateUpdateRequest() { + s_instance = this; + } +}; + +GetInfoRequest<librbd::MockTestImageCtx> *GetInfoRequest<librbd::MockTestImageCtx>::s_instance = nullptr; +ImageRemoveRequest<librbd::MockTestImageCtx> *ImageRemoveRequest<librbd::MockTestImageCtx>::s_instance = nullptr; +ImageStateUpdateRequest<librbd::MockTestImageCtx> *ImageStateUpdateRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +namespace snapshot { + +template <> +struct PromoteRequest<librbd::MockTestImageCtx> { + Context *on_finish = nullptr; + static PromoteRequest *s_instance; + static PromoteRequest *create(librbd::MockTestImageCtx*, + const std::string& global_image_id, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + PromoteRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +PromoteRequest<librbd::MockTestImageCtx> *PromoteRequest<librbd::MockTestImageCtx>::s_instance = nullptr; + +} // namespace snapshot +} // namespace mirror +} // namespace librbd + +// template definitions +#include "librbd/mirror/DisableRequest.cc" +template class librbd::mirror::DisableRequest<librbd::MockTestImageCtx>; + +ACTION_P(TestFeatures, image_ctx) { + return ((image_ctx->features & arg0) != 0); +} + +namespace librbd { +namespace mirror { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockMirrorDisableRequest : public TestMockFixture { +public: + typedef DisableRequest<MockTestImageCtx> MockDisableRequest; + typedef Journal<MockTestImageCtx> MockJournal; + typedef journal::PromoteRequest<MockTestImageCtx> MockJournalPromoteRequest; + typedef mirror::GetInfoRequest<MockTestImageCtx> MockGetInfoRequest; + typedef mirror::ImageRemoveRequest<MockTestImageCtx> MockImageRemoveRequest; + typedef mirror::ImageStateUpdateRequest<MockTestImageCtx> MockImageStateUpdateRequest; + typedef mirror::snapshot::PromoteRequest<MockTestImageCtx> MockSnapshotPromoteRequest; + + void expect_get_mirror_info(MockTestImageCtx &mock_image_ctx, + MockGetInfoRequest &mock_get_info_request, + const cls::rbd::MirrorImage &mirror_image, + PromotionState promotion_state, int r) { + + EXPECT_CALL(mock_get_info_request, send()) + .WillOnce( + Invoke([&mock_image_ctx, &mock_get_info_request, mirror_image, + promotion_state, r]() { + if (r == 0) { + *mock_get_info_request.mirror_image = mirror_image; + *mock_get_info_request.promotion_state = promotion_state; + } + mock_image_ctx.op_work_queue->queue( + mock_get_info_request.on_finish, r); + })); + } + + void expect_mirror_image_state_update( + MockTestImageCtx &mock_image_ctx, + MockImageStateUpdateRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce( + Invoke([&mock_image_ctx, &mock_request, r]() { + mock_image_ctx.op_work_queue->queue(mock_request.on_finish, r); + })); + } + + void expect_mirror_image_remove( + MockTestImageCtx &mock_image_ctx, + MockImageRemoveRequest& mock_request, int r) { + EXPECT_CALL(mock_request, send()) + .WillOnce( + Invoke([&mock_image_ctx, &mock_request, r]() { + mock_image_ctx.op_work_queue->queue(mock_request.on_finish, r); + })); + } + + void expect_journal_client_list(MockTestImageCtx &mock_image_ctx, + const std::set<cls::journal::Client> &clients, + int r) { + bufferlist bl; + using ceph::encode; + encode(clients, bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(::journal::Journaler::header_oid(mock_image_ctx.id), + _, StrEq("journal"), StrEq("client_list"), _, _, _, _)) + .WillOnce(DoAll(WithArg<5>(CopyInBufferlist(bl)), + Return(r))); + } + + void expect_journal_client_unregister(MockTestImageCtx &mock_image_ctx, + const std::string &client_id, + int r) { + bufferlist bl; + using ceph::encode; + encode(client_id, bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(::journal::Journaler::header_oid(mock_image_ctx.id), + _, StrEq("journal"), StrEq("client_unregister"), + ContentsEqual(bl), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_journal_promote(MockTestImageCtx &mock_image_ctx, + MockJournalPromoteRequest &mock_promote_request, + int r) { + EXPECT_CALL(mock_promote_request, send()) + .WillOnce(FinishRequest(&mock_promote_request, r, &mock_image_ctx)); + } + + void expect_snapshot_promote(MockTestImageCtx &mock_image_ctx, + MockSnapshotPromoteRequest &mock_promote_request, + int r) { + EXPECT_CALL(mock_promote_request, send()) + .WillOnce(FinishRequest(&mock_promote_request, r, &mock_image_ctx)); + } + + 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_snap_remove(MockTestImageCtx &mock_image_ctx, + const std::string &snap_name, int r) { + EXPECT_CALL(*mock_image_ctx.operations, snap_remove(_, StrEq(snap_name), _)) + .WillOnce(WithArg<2>(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue))); + } + + template <typename T> + bufferlist encode(const T &t) { + using ceph::encode; + bufferlist bl; + encode(t, bl); + return bl; + } + +}; + +TEST_F(TestMockMirrorDisableRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + expect_snap_remove(mock_image_ctx, "snap 1", 0); + expect_snap_remove(mock_image_ctx, "snap 2", 0); + + InSequence seq; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_client_list( + mock_image_ctx, { + {"", encode(journal::ClientData{journal::ImageClientMeta{}})}, + {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})}, + {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{ + "remote image id", {{cls::rbd::UserSnapshotNamespace(), "snap 1", boost::optional<uint64_t>(0)}, + {cls::rbd::UserSnapshotNamespace(), "snap 2", boost::optional<uint64_t>(0)}}} + })} + }, 0); + expect_journal_client_unregister(mock_image_ctx, "peer 1", 0); + expect_journal_client_unregister(mock_image_ctx, "peer 2", 0); + expect_journal_client_list(mock_image_ctx, {}, 0); + MockImageRemoveRequest mock_image_remove_request; + expect_mirror_image_remove( + mock_image_ctx, mock_image_remove_request, 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, SuccessNoRemove) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + 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; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_client_list(mock_image_ctx, {}, 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, false, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, SuccessNonPrimary) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournalPromoteRequest mock_promote_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_promote(mock_image_ctx, mock_promote_request, 0); + expect_is_refresh_required(mock_image_ctx, false); + expect_journal_client_list(mock_image_ctx, {}, 0); + MockImageRemoveRequest mock_image_remove_request; + expect_mirror_image_remove( + mock_image_ctx, mock_image_remove_request, 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, NonPrimaryError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + 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; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, false, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, GetMirrorInfoError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + 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; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, -EINVAL); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, MirrorImageSetError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + 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; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, -ENOENT); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-ENOENT, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, JournalPromoteError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockJournalPromoteRequest mock_promote_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_promote(mock_image_ctx, mock_promote_request, -EPERM); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, JournalClientListError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + 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; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_client_list(mock_image_ctx, {}, -EBADMSG); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EBADMSG, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, SnapRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + expect_snap_remove(mock_image_ctx, "snap 1", 0); + expect_snap_remove(mock_image_ctx, "snap 2", -EPERM); + + InSequence seq; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_client_list( + mock_image_ctx, { + {"", encode(journal::ClientData{journal::ImageClientMeta{}})}, + {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})}, + {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{ + "remote image id", {{cls::rbd::UserSnapshotNamespace(), "snap 1", boost::optional<uint64_t>(0)}, + {cls::rbd::UserSnapshotNamespace(), "snap 2", boost::optional<uint64_t>(0)}}} + })} + }, 0); + expect_journal_client_unregister(mock_image_ctx, "peer 1", 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, JournalClientUnregisterError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + expect_op_work_queue(mock_image_ctx); + expect_snap_remove(mock_image_ctx, "snap 1", 0); + expect_snap_remove(mock_image_ctx, "snap 2", 0); + + InSequence seq; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_client_list( + mock_image_ctx, { + {"", encode(journal::ClientData{journal::ImageClientMeta{}})}, + {"peer 1", encode(journal::ClientData{journal::MirrorPeerClientMeta{}})}, + {"peer 2", encode(journal::ClientData{journal::MirrorPeerClientMeta{ + "remote image id", {{cls::rbd::UserSnapshotNamespace(), "snap 1", boost::optional<uint64_t>(0)}, + {cls::rbd::UserSnapshotNamespace(), "snap 2", boost::optional<uint64_t>(0)}}} + })} + }, 0); + expect_journal_client_unregister(mock_image_ctx, "peer 1", -EINVAL); + expect_journal_client_unregister(mock_image_ctx, "peer 2", 0); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, SnapshotPromoteError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockSnapshotPromoteRequest mock_promote_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_snapshot_promote(mock_image_ctx, mock_promote_request, -EPERM); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, RefreshError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockSnapshotPromoteRequest mock_promote_request; + + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_SNAPSHOT, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_NON_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_snapshot_promote(mock_image_ctx, mock_promote_request, 0); + expect_is_refresh_required(mock_image_ctx, true); + expect_refresh_image(mock_image_ctx, -EPERM); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, true, true, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockMirrorDisableRequest, MirrorImageRemoveError) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + 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; + + MockGetInfoRequest mock_get_info_request; + expect_get_mirror_info( + mock_image_ctx, mock_get_info_request, + {cls::rbd::MIRROR_IMAGE_MODE_JOURNAL, "global id", + cls::rbd::MIRROR_IMAGE_STATE_ENABLED}, PROMOTION_STATE_PRIMARY, 0); + MockImageStateUpdateRequest mock_image_state_update_request; + expect_mirror_image_state_update( + mock_image_ctx, mock_image_state_update_request, 0); + expect_journal_client_list(mock_image_ctx, {}, 0); + MockImageRemoveRequest mock_image_remove_request; + expect_mirror_image_remove( + mock_image_ctx, mock_image_remove_request, -EINVAL); + + C_SaferCond ctx; + auto req = new MockDisableRequest(&mock_image_ctx, false, true, &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace mirror +} // namespace librbd |