// -*- 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/librados_test_stub/MockTestMemIoCtxImpl.h" #include "include/rbd_types.h" #include "common/ceph_mutex.h" #include "librbd/object_map/DiffRequest.h" #include "gtest/gtest.h" #include "gmock/gmock.h" namespace librbd { namespace { struct MockTestImageCtx : public MockImageCtx { MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { } }; } // anonymous namespace } // namespace librbd #include "librbd/object_map/DiffRequest.cc" using ::testing::_; using ::testing::Invoke; using ::testing::InSequence; using ::testing::StrEq; using ::testing::WithArg; namespace librbd { namespace object_map { class TestMockObjectMapDiffRequest : public TestMockFixture { public: typedef DiffRequest MockDiffRequest; void SetUp() override { TestMockFixture::SetUp(); ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); } void expect_get_flags(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, int32_t flags, int r) { EXPECT_CALL(mock_image_ctx, get_flags(snap_id, _)) .WillOnce(WithArg<1>(Invoke([flags, r](uint64_t *out_flags) { *out_flags = flags; return r; }))); } template void expect_load_map(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, const BitVector<2>& object_map, int r, Lambda&& lambda) { std::string snap_oid(ObjectMap<>::object_map_name(mock_image_ctx.id, snap_id)); EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), exec(snap_oid, _, StrEq("rbd"), StrEq("object_map_load"), _, _, _, _)) .WillOnce(WithArg<5>(Invoke([object_map, r, lambda=std::move(lambda)] (bufferlist* out_bl) { lambda(); auto out_object_map{object_map}; out_object_map.set_crc_enabled(false); encode(out_object_map, *out_bl); return r; }))); } void expect_load_map(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, const BitVector<2>& object_map, int r) { expect_load_map(mock_image_ctx, snap_id, object_map, r, [](){}); } librbd::ImageCtx* m_image_ctx = nullptr; BitVector<2> m_object_diff_state; }; TEST_F(TestMockObjectMapDiffRequest, InvalidStartSnap) { MockTestImageCtx mock_image_ctx(*m_image_ctx); InSequence seq; C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, CEPH_NOSNAP, 0, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-EINVAL, ctx.wait()); } TEST_F(TestMockObjectMapDiffRequest, StartEndSnapEqual) { MockTestImageCtx mock_image_ctx(*m_image_ctx); InSequence seq; C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 1, 1, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); ASSERT_EQ(0U, m_object_diff_state.size()); } TEST_F(TestMockObjectMapDiffRequest, FastDiffDisabled) { // negative test -- object-map implicitly enables fast-diff REQUIRE(!is_feature_enabled(RBD_FEATURE_OBJECT_MAP)); MockTestImageCtx mock_image_ctx(*m_image_ctx); InSequence seq; C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-EINVAL, ctx.wait()); } TEST_F(TestMockObjectMapDiffRequest, FastDiffInvalid) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, RBD_FLAG_FAST_DIFF_INVALID, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-EINVAL, ctx.wait()); } TEST_F(TestMockObjectMapDiffRequest, FullDelta) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}}, {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, 0, 0); BitVector<2> object_map_1; object_map_1.resize(object_count); object_map_1[1] = OBJECT_EXISTS_CLEAN; expect_load_map(mock_image_ctx, 1U, object_map_1, 0); expect_get_flags(mock_image_ctx, 2U, 0, 0); BitVector<2> object_map_2; object_map_2.resize(object_count); object_map_2[1] = OBJECT_EXISTS_CLEAN; object_map_2[2] = OBJECT_EXISTS; object_map_2[3] = OBJECT_EXISTS; expect_load_map(mock_image_ctx, 2U, object_map_2, 0); expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); BitVector<2> object_map_head; object_map_head.resize(object_count); object_map_head[1] = OBJECT_EXISTS_CLEAN; object_map_head[2] = OBJECT_EXISTS_CLEAN; expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; expected_diff_state[2] = DIFF_STATE_DATA_UPDATED; expected_diff_state[3] = DIFF_STATE_HOLE_UPDATED; ASSERT_EQ(expected_diff_state, m_object_diff_state); } TEST_F(TestMockObjectMapDiffRequest, IntermediateDelta) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}}, {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, 0, 0); BitVector<2> object_map_1; object_map_1.resize(object_count); object_map_1[1] = OBJECT_EXISTS; object_map_1[2] = OBJECT_EXISTS_CLEAN; expect_load_map(mock_image_ctx, 1U, object_map_1, 0); expect_get_flags(mock_image_ctx, 2U, 0, 0); BitVector<2> object_map_2; object_map_2.resize(object_count); object_map_2[1] = OBJECT_EXISTS_CLEAN; object_map_2[2] = OBJECT_EXISTS; object_map_2[3] = OBJECT_EXISTS; expect_load_map(mock_image_ctx, 2U, object_map_2, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 1, 2, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); expected_diff_state[1] = DIFF_STATE_DATA; expected_diff_state[2] = DIFF_STATE_DATA_UPDATED; expected_diff_state[3] = DIFF_STATE_DATA_UPDATED; ASSERT_EQ(expected_diff_state, m_object_diff_state); } TEST_F(TestMockObjectMapDiffRequest, EndDelta) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}}, {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 2U, 0, 0); BitVector<2> object_map_2; object_map_2.resize(object_count); object_map_2[1] = OBJECT_EXISTS_CLEAN; object_map_2[2] = OBJECT_EXISTS; object_map_2[3] = OBJECT_EXISTS; expect_load_map(mock_image_ctx, 2U, object_map_2, 0); expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); BitVector<2> object_map_head; object_map_head.resize(object_count); object_map_head[1] = OBJECT_EXISTS_CLEAN; object_map_head[2] = OBJECT_EXISTS_CLEAN; expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 2, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); expected_diff_state[1] = DIFF_STATE_DATA; expected_diff_state[2] = DIFF_STATE_DATA; expected_diff_state[3] = DIFF_STATE_HOLE_UPDATED; ASSERT_EQ(expected_diff_state, m_object_diff_state); } TEST_F(TestMockObjectMapDiffRequest, StartSnapDNE) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 1, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-ENOENT, ctx.wait()); } TEST_F(TestMockObjectMapDiffRequest, EndSnapDNE) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, 0, 0); BitVector<2> object_map_1; object_map_1.resize(object_count); expect_load_map(mock_image_ctx, 1U, object_map_1, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 1, 2, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-ENOENT, ctx.wait()); } TEST_F(TestMockObjectMapDiffRequest, IntermediateSnapDNE) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}}, {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, 0, 0); BitVector<2> object_map_1; object_map_1.resize(object_count); object_map_1[1] = OBJECT_EXISTS_CLEAN; expect_load_map(mock_image_ctx, 1U, object_map_1, 0, [&mock_image_ctx]() { mock_image_ctx.snap_info.erase(2); }); expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); BitVector<2> object_map_head; object_map_head.resize(object_count); object_map_head[1] = OBJECT_EXISTS_CLEAN; expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; ASSERT_EQ(expected_diff_state, m_object_diff_state); } TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapDNE) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); InSequence seq; expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); BitVector<2> object_map_head; expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, -ENOENT); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-ENOENT, ctx.wait()); } TEST_F(TestMockObjectMapDiffRequest, LoadIntermediateObjectMapDNE) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, 0, 0); BitVector<2> object_map_1; expect_load_map(mock_image_ctx, 1U, object_map_1, -ENOENT); expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); BitVector<2> object_map_head; object_map_head.resize(object_count); object_map_head[1] = OBJECT_EXISTS_CLEAN; expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; ASSERT_EQ(expected_diff_state, m_object_diff_state); } TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapError) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, 0, 0); BitVector<2> object_map_1; expect_load_map(mock_image_ctx, 1U, object_map_1, -EPERM); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-EPERM, ctx.wait()); } TEST_F(TestMockObjectMapDiffRequest, ObjectMapTooSmall) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); MockTestImageCtx mock_image_ctx(*m_image_ctx); mock_image_ctx.snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, {}, {}, {}}} }; InSequence seq; expect_get_flags(mock_image_ctx, 1U, 0, 0); BitVector<2> object_map_1; expect_load_map(mock_image_ctx, 1U, object_map_1, 0); C_SaferCond ctx; auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, &m_object_diff_state, &ctx); req->send(); ASSERT_EQ(-EINVAL, ctx.wait()); } } // namespace object_map } // librbd