From 19fcec84d8d7d21e796c7624e521b60d28ee21ed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:45:59 +0200 Subject: Adding upstream version 16.2.11+ds. Signed-off-by: Daniel Baumann --- .../librbd/object_map/mock/MockInvalidateRequest.h | 41 ++ .../librbd/object_map/test_mock_DiffRequest.cc | 493 +++++++++++++++++++++ .../object_map/test_mock_InvalidateRequest.cc | 158 +++++++ .../librbd/object_map/test_mock_LockRequest.cc | 221 +++++++++ .../librbd/object_map/test_mock_RefreshRequest.cc | 465 +++++++++++++++++++ .../librbd/object_map/test_mock_ResizeRequest.cc | 154 +++++++ .../object_map/test_mock_SnapshotCreateRequest.cc | 232 ++++++++++ .../object_map/test_mock_SnapshotRemoveRequest.cc | 345 ++++++++++++++ .../test_mock_SnapshotRollbackRequest.cc | 148 +++++++ .../librbd/object_map/test_mock_UnlockRequest.cc | 69 +++ .../librbd/object_map/test_mock_UpdateRequest.cc | 291 ++++++++++++ 11 files changed, 2617 insertions(+) create mode 100644 src/test/librbd/object_map/mock/MockInvalidateRequest.h create mode 100644 src/test/librbd/object_map/test_mock_DiffRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_InvalidateRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_LockRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_RefreshRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_ResizeRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_UnlockRequest.cc create mode 100644 src/test/librbd/object_map/test_mock_UpdateRequest.cc (limited to 'src/test/librbd/object_map') diff --git a/src/test/librbd/object_map/mock/MockInvalidateRequest.h b/src/test/librbd/object_map/mock/MockInvalidateRequest.h new file mode 100644 index 000000000..92f30748c --- /dev/null +++ b/src/test/librbd/object_map/mock/MockInvalidateRequest.h @@ -0,0 +1,41 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/object_map/InvalidateRequest.h" + +// template definitions +#include "librbd/object_map/InvalidateRequest.cc" + +namespace librbd { +namespace object_map { + +template +struct MockInvalidateRequestBase { + static std::list*> s_requests; + uint64_t snap_id = 0; + bool force = false; + Context *on_finish = nullptr; + + static InvalidateRequest* create(I &image_ctx, uint64_t snap_id, + bool force, Context *on_finish) { + ceph_assert(!s_requests.empty()); + InvalidateRequest* req = s_requests.front(); + req->snap_id = snap_id; + req->force = force; + req->on_finish = on_finish; + s_requests.pop_front(); + return req; + } + + MockInvalidateRequestBase() { + s_requests.push_back(static_cast*>(this)); + } + + MOCK_METHOD0(send, void()); +}; + +template +std::list*> MockInvalidateRequestBase::s_requests; + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_DiffRequest.cc b/src/test/librbd/object_map/test_mock_DiffRequest.cc new file mode 100644 index 000000000..c25ae4a95 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_DiffRequest.cc @@ -0,0 +1,493 @@ +// -*- 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 diff --git a/src/test/librbd/object_map/test_mock_InvalidateRequest.cc b/src/test/librbd/object_map/test_mock_InvalidateRequest.cc new file mode 100644 index 000000000..5ea40c03d --- /dev/null +++ b/src/test/librbd/object_map/test_mock_InvalidateRequest.cc @@ -0,0 +1,158 @@ +// -*- 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 "librbd/internal.h" +#include "librbd/api/Image.h" +#include "librbd/object_map/InvalidateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapInvalidateRequest : public TestMockFixture { +public: +}; + +TEST_F(TestMockObjectMapInvalidateRequest, UpdatesInMemoryFlag) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_FALSE(flags_set); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, true, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, + _, _)) + .WillOnce(DoDefault()); + + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_TRUE(flags_set); +} + +TEST_F(TestMockObjectMapInvalidateRequest, UpdatesHeadOnDiskFlag) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, + _, _)) + .WillOnce(DoDefault()); + + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapInvalidateRequest, UpdatesSnapOnDiskFlag) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, ictx->snap_id, false, + &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, + _, _)) + .WillOnce(DoDefault()); + + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockObjectMapInvalidateRequest, ErrorOnDiskUpdateWithoutLock) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, + _, _)) + .Times(0); + + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(-EROFS, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapInvalidateRequest, ErrorOnDiskUpdateFailure) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new InvalidateRequest<>(*ictx, CEPH_NOSNAP, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, + _, _)) + .WillOnce(Return(-EINVAL)); + + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_LockRequest.cc b/src/test/librbd/object_map/test_mock_LockRequest.cc new file mode 100644 index 000000000..a99cab61e --- /dev/null +++ b/src/test/librbd/object_map/test_mock_LockRequest.cc @@ -0,0 +1,221 @@ +// -*- 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 "cls/lock/cls_lock_ops.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/LockRequest.h" + +// template definitions +#include "librbd/object_map/LockRequest.cc" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockObjectMapLockRequest : public TestMockFixture { +public: + typedef LockRequest MockLockRequest; + + void expect_lock(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("lock"), _, _, _, _)) + .WillOnce(Return(r)); + } + + void expect_get_lock_info(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("get_info"), _, + _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + entity_name_t entity1(entity_name_t::CLIENT(1)); + entity_name_t entity2(entity_name_t::CLIENT(2)); + + cls_lock_get_info_reply reply; + reply.lockers = decltype(reply.lockers){ + {rados::cls::lock::locker_id_t(entity1, "cookie1"), + rados::cls::lock::locker_info_t()}, + {rados::cls::lock::locker_id_t(entity2, "cookie2"), + rados::cls::lock::locker_info_t()}}; + + bufferlist bl; + encode(reply, bl, CEPH_FEATURES_SUPPORTED_DEFAULT); + + std::string str(bl.c_str(), bl.length()); + expect.WillOnce(DoAll(WithArg<5>(CopyInBufferlist(str)), Return(r))); + } + } + + void expect_break_lock(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("break_lock"), + _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.Times(2).WillRepeatedly(Return(0)); + } + } +}; + +TEST_F(TestMockObjectMapLockRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, LockBusy) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, 0); + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, LockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -ENOENT); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, GetLockInfoMissing) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, -ENOENT); + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, GetLockInfoError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, -EINVAL); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, BreakLockMissing) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, -ENOENT); + expect_lock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, BreakLockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, -EINVAL); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapLockRequest, LockErrorAfterBrokeLock) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockLockRequest *req = new MockLockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_lock(mock_image_ctx, -EBUSY); + expect_get_lock_info(mock_image_ctx, 0); + expect_break_lock(mock_image_ctx, 0); + expect_lock(mock_image_ctx, -EBUSY); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_RefreshRequest.cc b/src/test/librbd/object_map/test_mock_RefreshRequest.cc new file mode 100644 index 000000000..43d9c3b43 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_RefreshRequest.cc @@ -0,0 +1,465 @@ +// -*- 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/object_map/mock/MockInvalidateRequest.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/RefreshRequest.h" +#include "librbd/object_map/LockRequest.h" + +namespace librbd { + +namespace { + +struct MockObjectMapImageCtx : public MockImageCtx { + MockObjectMapImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace object_map { + +template <> +class LockRequest { +public: + static LockRequest *s_instance; + static LockRequest *create(MockObjectMapImageCtx &image_ctx, Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + Context *on_finish = nullptr; + + LockRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +template<> +struct InvalidateRequest : + public MockInvalidateRequestBase { +}; + +LockRequest *LockRequest::s_instance = nullptr; + +} // namespace object_map +} // namespace librbd + +// template definitions +#include "librbd/object_map/RefreshRequest.cc" +#include "librbd/object_map/LockRequest.cc" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockObjectMapRefreshRequest : public TestMockFixture { +public: + static const uint64_t TEST_SNAP_ID = 123; + + typedef RefreshRequest MockRefreshRequest; + typedef LockRequest MockLockRequest; + typedef InvalidateRequest MockInvalidateRequest; + + void expect_object_map_lock(MockObjectMapImageCtx &mock_image_ctx, + MockLockRequest &mock_lock_request) { + EXPECT_CALL(mock_lock_request, send()) + .WillOnce(FinishRequest(&mock_lock_request, 0, + &mock_image_ctx)); + } + + void expect_object_map_load(MockObjectMapImageCtx &mock_image_ctx, + ceph::BitVector<2> *object_map, uint64_t snap_id, + int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, snap_id)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("rbd"), + StrEq("object_map_load"), _, _, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + ceph_assert(object_map); + object_map->set_crc_enabled(false); + + bufferlist bl; + encode(*object_map, bl); + + std::string str(bl.c_str(), bl.length()); + expect.WillOnce(DoAll(WithArg<5>(CopyInBufferlist(str)), Return(0))); + } + } + + void expect_get_image_size(MockObjectMapImageCtx &mock_image_ctx, uint64_t snap_id, + uint64_t size) { + EXPECT_CALL(mock_image_ctx, get_image_size(snap_id)) + .WillOnce(Return(size)); + } + + void expect_invalidate_request(MockObjectMapImageCtx &mock_image_ctx, + MockInvalidateRequest &invalidate_request, + int r) { + EXPECT_CALL(invalidate_request, send()) + .WillOnce(FinishRequest(&invalidate_request, r, + &mock_image_ctx)); + } + + void expect_truncate_request(MockObjectMapImageCtx &mock_image_ctx) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + TEST_SNAP_ID)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), truncate(oid, 0, _)) + .WillOnce(Return(0)); + } + + void expect_object_map_resize(MockObjectMapImageCtx &mock_image_ctx, + uint64_t num_objects, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + TEST_SNAP_ID)); + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("rbd"), + StrEq("object_map_resize"), _, _, _, _)); + expect.WillOnce(Return(r)); + } + + void init_object_map(MockObjectMapImageCtx &mock_image_ctx, + ceph::BitVector<2> *object_map) { + uint64_t num_objs = Striper::get_num_objects( + mock_image_ctx.layout, mock_image_ctx.image_ctx->size); + object_map->resize(num_objs); + for (uint64_t i = 0; i < num_objs; ++i) { + (*object_map)[i] = rand() % 3; + } + } +}; + +TEST_F(TestMockObjectMapRefreshRequest, SuccessHead) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockLockRequest mock_lock_request; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, CEPH_NOSNAP, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, CEPH_NOSNAP, + mock_image_ctx.image_ctx->size); + expect_object_map_lock(mock_image_ctx, mock_lock_request); + expect_object_map_load(mock_image_ctx, &on_disk_object_map, CEPH_NOSNAP, 0); + expect_get_image_size(mock_image_ctx, CEPH_NOSNAP, + mock_image_ctx.image_ctx->size); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(on_disk_object_map, object_map); +} + +TEST_F(TestMockObjectMapRefreshRequest, SuccessSnapshot) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &on_disk_object_map, TEST_SNAP_ID, 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + ASSERT_EQ(on_disk_object_map, object_map); +} + +TEST_F(TestMockObjectMapRefreshRequest, LoadError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, nullptr, TEST_SNAP_ID, -ENOENT); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, LoadInvalidateError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, nullptr, TEST_SNAP_ID, -ENOENT); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, -EPERM); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, LoadCorrupt) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, nullptr, TEST_SNAP_ID, -EINVAL); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_truncate_request(mock_image_ctx); + expect_object_map_resize(mock_image_ctx, on_disk_object_map.size(), 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, TooSmall) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> small_object_map; + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &small_object_map, TEST_SNAP_ID, 0); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_object_map_resize(mock_image_ctx, on_disk_object_map.size(), 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, TooSmallInvalidateError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> small_object_map; + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &small_object_map, TEST_SNAP_ID, 0); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, -EPERM); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, TooLarge) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> large_object_map; + large_object_map.resize(on_disk_object_map.size() * 2); + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &large_object_map, TEST_SNAP_ID, 0); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, ResizeError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + ceph::BitVector<2> small_object_map; + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + expect_object_map_load(mock_image_ctx, &small_object_map, TEST_SNAP_ID, 0); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + expect_object_map_resize(mock_image_ctx, on_disk_object_map.size(), -ESTALE); + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + mock_image_ctx.image_ctx->size); + + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapRefreshRequest, LargeImageError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockObjectMapImageCtx mock_image_ctx(*ictx); + + ceph::BitVector<2> on_disk_object_map; + init_object_map(mock_image_ctx, &on_disk_object_map); + + C_SaferCond ctx; + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest( + mock_image_ctx, &object_map_lock, &object_map, TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + std::numeric_limits::max()); + + MockInvalidateRequest invalidate_request; + expect_invalidate_request(mock_image_ctx, invalidate_request, 0); + + req->send(); + ASSERT_EQ(-EFBIG, ctx.wait()); +} + +} // namespace object_map +} // namespace librbd + diff --git a/src/test/librbd/object_map/test_mock_ResizeRequest.cc b/src/test/librbd/object_map/test_mock_ResizeRequest.cc new file mode 100644 index 000000000..d6a3dd91c --- /dev/null +++ b/src/test/librbd/object_map/test_mock_ResizeRequest.cc @@ -0,0 +1,154 @@ +// -*- 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 "common/bit_vector.hpp" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/api/Image.h" +#include "librbd/object_map/ResizeRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapResizeRequest : public TestMockFixture { +public: + void expect_resize(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + std::string oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (snap_id == CEPH_NOSNAP) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _, + _)) + .WillOnce(DoDefault()); + } + + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, + _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_resize"), _, _, + _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, + _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapResizeRequest, UpdateInMemory) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, object_map.size(), + OBJECT_EXISTS, &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + for (uint64_t i = 0; i < object_map.size(); ++i) { + ASSERT_EQ(i == 0 ? OBJECT_NONEXISTENT : OBJECT_EXISTS, + object_map[i]); + } +} + +TEST_F(TestMockObjectMapResizeRequest, UpdateHeadOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_resize(ictx, CEPH_NOSNAP, 0); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, object_map.size(), + OBJECT_EXISTS, &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapResizeRequest, UpdateSnapOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + uint64_t snap_id = ictx->snap_id; + expect_resize(ictx, snap_id, 0); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map_lock, &object_map, snap_id, object_map.size(), + OBJECT_EXISTS, &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapResizeRequest, UpdateOnDiskError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_resize(ictx, CEPH_NOSNAP, -EINVAL); + expect_invalidate(ictx); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, object_map.size(), + OBJECT_EXISTS, &cond_ctx); + req->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc b/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc new file mode 100644 index 000000000..7f77aaf83 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,232 @@ +// -*- 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 "common/bit_vector.hpp" +#include "cls/rbd/cls_rbd_types.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/SnapshotCreateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapSnapshotCreateRequest : public TestMockFixture { +public: + void inject_snap_info(librbd::ImageCtx *ictx, uint64_t snap_id) { + std::unique_lock image_locker{ictx->image_lock}; + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "snap name", snap_id, + ictx->size, ictx->parent_md, + RBD_PROTECTION_STATUS_UNPROTECTED, 0, utime_t()); + } + + void expect_read_map(librbd::ImageCtx *ictx, int r) { + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), + 0, 0, _, _, _)).WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), + 0, 0, _, _, _)).WillOnce(DoDefault()); + } + } + + void expect_write_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, snap_id), _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, snap_id), _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_add_snapshot(librbd::ImageCtx *ictx, int r) { + std::string oid(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _, + _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _, + _)) + .WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_snap_add"), _, _, + _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, + _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapSnapshotCreateRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, 0); + expect_write_map(ictx, snap_id, 0); + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_add_snapshot(ictx, 0); + } + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, ReadMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, -ENOENT); + expect_invalidate(ictx); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, WriteMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, 0); + expect_write_map(ictx, snap_id, -EINVAL); + expect_invalidate(ictx); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, AddSnapshotError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + expect_read_map(ictx, 0); + expect_write_map(ictx, snap_id, 0); + expect_add_snapshot(ictx, -EINVAL); + expect_invalidate(ictx); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(-ENOENT, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotCreateRequest, FlagCleanObjects) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1024); + for (uint64_t i = 0; i < object_map.size(); ++i) { + object_map[i] = i % 2 == 0 ? OBJECT_EXISTS : OBJECT_NONEXISTENT; + } + + uint64_t snap_id = 1; + inject_snap_info(ictx, snap_id); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotCreateRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + for (uint64_t i = 0; i < object_map.size(); ++i) { + ASSERT_EQ(i % 2 == 0 ? OBJECT_EXISTS_CLEAN : OBJECT_NONEXISTENT, + object_map[i]); + } +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc b/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc new file mode 100644 index 000000000..20318743d --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc @@ -0,0 +1,345 @@ +// -*- 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 "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/SnapshotRemoveRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapSnapshotRemoveRequest : public TestMockFixture { +public: + void expect_load_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + std::string snap_oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(snap_oid, _, StrEq("rbd"), StrEq("object_map_load"), _, + _, _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(snap_oid, _, StrEq("rbd"), StrEq("object_map_load"), _, + _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_remove_snapshot(librbd::ImageCtx *ictx, int r) { + std::string oid(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _, + _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _, + _)) + .WillOnce(DoDefault()); + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_snap_remove"), _, + _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_remove_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + std::string snap_oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), remove(snap_oid, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), remove(snap_oid, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, + _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, 0); + } + expect_remove_map(ictx, snap_id, 0); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, LoadMapMissing) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + auto snap_it = ictx->snap_info.find(snap_id); + ASSERT_NE(ictx->snap_info.end(), snap_it); + snap_it->second.flags |= RBD_FLAG_OBJECT_MAP_INVALID; + + expect_load_map(ictx, snap_id, -ENOENT); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + { + // shouldn't invalidate the HEAD revision when we fail to load + // the already deleted snapshot + std::shared_lock image_locker{ictx->image_lock}; + uint64_t flags; + ASSERT_EQ(0, ictx->get_flags(CEPH_NOSNAP, &flags)); + ASSERT_EQ(0U, flags & RBD_FLAG_OBJECT_MAP_INVALID); + } + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, LoadMapError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_load_map(ictx, snap_id, -EINVAL); + expect_invalidate(ictx); + expect_remove_map(ictx, snap_id, 0); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveSnapshotMissing) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, -ENOENT); + expect_remove_map(ictx, snap_id, 0); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveSnapshotError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, -EINVAL); + expect_invalidate(ictx); + expect_remove_map(ictx, snap_id, 0); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveMapMissing) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, 0); + } + expect_remove_map(ictx, snap_id, -ENOENT); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, RemoveMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + if (ictx->test_features(RBD_FEATURE_FAST_DIFF)) { + expect_load_map(ictx, snap_id, 0); + expect_remove_snapshot(ictx, 0); + } + expect_remove_map(ictx, snap_id, -EINVAL); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRemoveRequest, ScrubCleanObjects) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + librbd::NoOpProgressContext prog_ctx; + uint64_t size = 4294967296; // 4GB = 1024 * 4MB + ASSERT_EQ(0, resize(ictx, size)); + + // update image objectmap for snap inherit + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1024); + for (uint64_t i = 512; i < object_map.size(); ++i) { + object_map[i] = i % 2 == 0 ? OBJECT_EXISTS : OBJECT_NONEXISTENT; + } + + C_SaferCond cond_ctx1; + { + librbd::ObjectMap<> *om = new librbd::ObjectMap<>(*ictx, ictx->snap_id); + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + om->set_object_map(object_map); + om->aio_save(&cond_ctx1); + om->put(); + } + ASSERT_EQ(0, cond_ctx1.wait()); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + // simutate the image objectmap state after creating snap + for (uint64_t i = 512; i < object_map.size(); ++i) { + object_map[i] = i % 2 == 0 ? OBJECT_EXISTS_CLEAN : OBJECT_NONEXISTENT; + } + + C_SaferCond cond_ctx2; + uint64_t snap_id = ictx->snap_info.rbegin()->first; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map_lock, &object_map, snap_id, &cond_ctx2); + { + std::shared_lock owner_locker{ictx->owner_lock}; + std::unique_lock image_locker{ictx->image_lock}; + request->send(); + } + ASSERT_EQ(0, cond_ctx2.wait()); + + for (uint64_t i = 512; i < object_map.size(); ++i) { + ASSERT_EQ(i % 2 == 0 ? OBJECT_EXISTS : OBJECT_NONEXISTENT, + object_map[i]); + } +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc b/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc new file mode 100644 index 000000000..7b89a0996 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc @@ -0,0 +1,148 @@ +// -*- 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 "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/SnapshotRollbackRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapSnapshotRollbackRequest : public TestMockFixture { +public: + void expect_read_map(librbd::ImageCtx *ictx, uint64_t snap_id, int r) { + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, snap_id), + 0, 0, _, _, _)).WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + read(ObjectMap<>::object_map_name(ictx->id, snap_id), + 0, 0, _, _, _)).WillOnce(DoDefault()); + } + } + + void expect_write_map(librbd::ImageCtx *ictx, int r) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), _, + StrEq("lock"), StrEq("assert_locked"), _, _, _, _)) + .WillOnce(DoDefault()); + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + write_full( + ObjectMap<>::object_map_name(ictx->id, CEPH_NOSNAP), _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx, uint32_t times) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, + _, _, _)) + .Times(times) + .WillRepeatedly(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapSnapshotRollbackRequest, Success) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_read_map(ictx, snap_id, 0); + expect_write_map(ictx, 0); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRollbackRequest( + *ictx, snap_id, &cond_ctx); + request->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRollbackRequest, ReadMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_read_map(ictx, snap_id, -ENOENT); + expect_invalidate(ictx, 2); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRollbackRequest( + *ictx, snap_id, &cond_ctx); + request->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + { + std::shared_lock image_locker{ictx->image_lock}; + uint64_t flags; + ASSERT_EQ(0, ictx->get_flags(snap_id, &flags)); + ASSERT_NE(0U, flags & RBD_FLAG_OBJECT_MAP_INVALID); + } + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_TRUE(flags_set); + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapSnapshotRollbackRequest, WriteMapError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_read_map(ictx, snap_id, 0); + expect_write_map(ictx, -EINVAL); + expect_invalidate(ictx, 1); + + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRollbackRequest( + *ictx, snap_id, &cond_ctx); + request->send(); + ASSERT_EQ(0, cond_ctx.wait()); + + { + std::shared_lock image_locker{ictx->image_lock}; + uint64_t flags; + ASSERT_EQ(0, ictx->get_flags(snap_id, &flags)); + ASSERT_EQ(0U, flags & RBD_FLAG_OBJECT_MAP_INVALID); + } + bool flags_set; + ASSERT_EQ(0, ictx->test_flags(CEPH_NOSNAP, + RBD_FLAG_OBJECT_MAP_INVALID, &flags_set)); + ASSERT_TRUE(flags_set); + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_UnlockRequest.cc b/src/test/librbd/object_map/test_mock_UnlockRequest.cc new file mode 100644 index 000000000..f91ee001d --- /dev/null +++ b/src/test/librbd/object_map/test_mock_UnlockRequest.cc @@ -0,0 +1,69 @@ +// -*- 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 "cls/lock/cls_lock_ops.h" +#include "librbd/ObjectMap.h" +#include "librbd/object_map/UnlockRequest.h" + +// template definitions +#include "librbd/object_map/UnlockRequest.cc" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapUnlockRequest : public TestMockFixture { +public: + typedef UnlockRequest MockUnlockRequest; + + void expect_unlock(MockImageCtx &mock_image_ctx, int r) { + std::string oid(ObjectMap<>::object_map_name(mock_image_ctx.id, + CEPH_NOSNAP)); + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(oid, _, StrEq("lock"), StrEq("unlock"), _, _, _, _)) + .WillOnce(Return(r)); + } +}; + +TEST_F(TestMockObjectMapUnlockRequest, Success) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockUnlockRequest *req = new MockUnlockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_unlock(mock_image_ctx, 0); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockObjectMapUnlockRequest, UnlockError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockImageCtx mock_image_ctx(*ictx); + + C_SaferCond ctx; + MockUnlockRequest *req = new MockUnlockRequest(mock_image_ctx, &ctx); + + InSequence seq; + expect_unlock(mock_image_ctx, -ENOENT); + req->send(); + + ASSERT_EQ(0, ctx.wait()); +} + +} // namespace object_map +} // namespace librbd diff --git a/src/test/librbd/object_map/test_mock_UpdateRequest.cc b/src/test/librbd/object_map/test_mock_UpdateRequest.cc new file mode 100644 index 000000000..c240dec00 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_UpdateRequest.cc @@ -0,0 +1,291 @@ +// -*- 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 "common/bit_vector.hpp" +#include "librbd/ImageState.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/Operations.h" +#include "librbd/api/Image.h" +#include "librbd/object_map/UpdateRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace object_map { + +using ::testing::_; +using ::testing::DoDefault; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +class TestMockObjectMapUpdateRequest : public TestMockFixture { +public: + void expect_update(librbd::ImageCtx *ictx, uint64_t snap_id, + uint64_t start_object_no, uint64_t end_object_no, + uint8_t new_state, + const boost::optional& current_state, int r) { + bufferlist bl; + encode(start_object_no, bl); + encode(end_object_no, bl); + encode(new_state, bl); + encode(current_state, bl); + + std::string oid(ObjectMap<>::object_map_name(ictx->id, snap_id)); + if (snap_id == CEPH_NOSNAP) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("lock"), StrEq("assert_locked"), _, _, _, + _)) + .WillOnce(DoDefault()); + } + + if (r < 0) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_update"), + ContentsEqual(bl), _, _, _)) + .WillOnce(Return(r)); + } else { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(oid, _, StrEq("rbd"), StrEq("object_map_update"), + ContentsEqual(bl), _, _, _)) + .WillOnce(DoDefault()); + } + } + + void expect_invalidate(librbd::ImageCtx *ictx) { + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, + _, _, _)) + .WillOnce(DoDefault()); + } +}; + +TEST_F(TestMockObjectMapUpdateRequest, UpdateInMemory) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + librbd::NoOpProgressContext no_progress; + ASSERT_EQ(0, ictx->operations->resize(4 << ictx->order, true, no_progress)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(4); + for (uint64_t i = 0; i < object_map.size(); ++i) { + object_map[i] = i % 4; + } + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(), + OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + std::unique_lock object_map_locker{object_map_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + for (uint64_t i = 0; i < object_map.size(); ++i) { + if (i % 4 == OBJECT_EXISTS || i % 4 == OBJECT_EXISTS_CLEAN) { + ASSERT_EQ(OBJECT_NONEXISTENT, object_map[i]); + } else { + ASSERT_EQ(i % 4, object_map[i]); + } + } +} + +TEST_F(TestMockObjectMapUpdateRequest, UpdateHeadOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_update(ictx, CEPH_NOSNAP, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, 0); + + ceph::shared_mutex object_map_lock = + ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(), + OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + std::unique_lock object_map_locker{object_map_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapUpdateRequest, UpdateSnapOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, librbd::api::Image<>::snap_set(ictx, + cls::rbd::UserSnapshotNamespace(), + "snap1")); + + uint64_t snap_id = ictx->snap_id; + expect_update(ictx, snap_id, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, 0); + + ceph::shared_mutex object_map_lock = + ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map_lock, &object_map, snap_id, 0, object_map.size(), + OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + std::unique_lock object_map_locker{object_map_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapUpdateRequest, UpdateOnDiskError) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_update(ictx, CEPH_NOSNAP, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, + -EINVAL); + expect_invalidate(ictx); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(), + OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + std::unique_lock object_map_locker{object_map_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapUpdateRequest, RebuildSnapOnDisk) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap1")); + ASSERT_EQ(0, ictx->state->refresh_if_required()); + ASSERT_EQ(CEPH_NOSNAP, ictx->snap_id); + + uint64_t snap_id = ictx->snap_info.rbegin()->first; + expect_update(ictx, snap_id, 0, 1, OBJECT_EXISTS_CLEAN, + boost::optional(), 0); + expect_unlock_exclusive_lock(*ictx); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map_lock, &object_map, snap_id, 0, object_map.size(), + OBJECT_EXISTS_CLEAN, boost::optional(), {}, false, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + std::unique_lock object_map_locker{object_map_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + // do not update the in-memory map if rebuilding a snapshot + ASSERT_NE(OBJECT_EXISTS_CLEAN, object_map[0]); +} + +TEST_F(TestMockObjectMapUpdateRequest, BatchUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + librbd::NoOpProgressContext no_progress; + ASSERT_EQ(0, ictx->operations->resize(712312 * ictx->get_object_size(), false, + no_progress)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_unlock_exclusive_lock(*ictx); + InSequence seq; + expect_update(ictx, CEPH_NOSNAP, 0, 262144, OBJECT_NONEXISTENT, OBJECT_EXISTS, + 0); + expect_update(ictx, CEPH_NOSNAP, 262144, 524288, OBJECT_NONEXISTENT, + OBJECT_EXISTS, 0); + expect_update(ictx, CEPH_NOSNAP, 524288, 712312, OBJECT_NONEXISTENT, + OBJECT_EXISTS, 0); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(712312); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(), + OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, false, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + std::unique_lock object_map_locker{object_map_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockObjectMapUpdateRequest, IgnoreMissingObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ASSERT_EQ(0, acquire_exclusive_lock(*ictx)); + + expect_update(ictx, CEPH_NOSNAP, 0, 1, OBJECT_NONEXISTENT, OBJECT_EXISTS, + -ENOENT); + + ceph::shared_mutex object_map_lock = ceph::make_shared_mutex("lock"); + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map_lock, &object_map, CEPH_NOSNAP, 0, object_map.size(), + OBJECT_NONEXISTENT, OBJECT_EXISTS, {}, true, &cond_ctx); + { + std::shared_lock image_locker{ictx->image_lock}; + std::unique_lock object_map_locker{object_map_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd -- cgit v1.2.3