diff options
Diffstat (limited to 'src/test/librbd/object_map')
10 files changed, 2063 insertions, 0 deletions
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 00000000..92f30748 --- /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 <typename I> +struct MockInvalidateRequestBase { + static std::list<InvalidateRequest<I>*> s_requests; + uint64_t snap_id = 0; + bool force = false; + Context *on_finish = nullptr; + + static InvalidateRequest<I>* create(I &image_ctx, uint64_t snap_id, + bool force, Context *on_finish) { + ceph_assert(!s_requests.empty()); + InvalidateRequest<I>* 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<InvalidateRequest<I>*>(this)); + } + + MOCK_METHOD0(send, void()); +}; + +template <typename I> +std::list<InvalidateRequest<I>*> MockInvalidateRequestBase<I>::s_requests; + +} // namespace object_map +} // namespace 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 00000000..f4b2ed43 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_InvalidateRequest.cc @@ -0,0 +1,153 @@ +// -*- 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, false, &cond_ctx); + + EXPECT_CALL(get_mock_io_ctx(ictx->md_ctx), + exec(ictx->header_oid, _, StrEq("rbd"), StrEq("set_flags"), _, _, _)) + .Times(0); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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()); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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()); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockObjectMapInvalidateRequest, SkipOnDiskUpdateWithoutLock) { + 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); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +TEST_F(TestMockObjectMapInvalidateRequest, IgnoresOnDiskUpdateFailure) { + 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)); + + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->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_LockRequest.cc b/src/test/librbd/object_map/test_mock_LockRequest.cc new file mode 100644 index 00000000..5954a461 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_LockRequest.cc @@ -0,0 +1,220 @@ +// -*- 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<MockImageCtx> 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.emplace( + rados::cls::lock::locker_id_t(entity1, "cookie1"), + rados::cls::lock::locker_info_t()); + reply.lockers.emplace( + 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 00000000..60cc579a --- /dev/null +++ b/src/test/librbd/object_map/test_mock_RefreshRequest.cc @@ -0,0 +1,453 @@ +// -*- 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<MockObjectMapImageCtx> { +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<MockObjectMapImageCtx> : + public MockInvalidateRequestBase<MockObjectMapImageCtx> { +}; + +LockRequest<MockObjectMapImageCtx> *LockRequest<MockObjectMapImageCtx>::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<MockObjectMapImageCtx> MockRefreshRequest; + typedef LockRequest<MockObjectMapImageCtx> MockLockRequest; + typedef InvalidateRequest<MockObjectMapImageCtx> 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::BitVector<2> object_map; + MockLockRequest mock_lock_request; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &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::BitVector<2> object_map; + MockRefreshRequest *req = new MockRefreshRequest(mock_image_ctx, &object_map, + TEST_SNAP_ID, &ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, TEST_SNAP_ID, + std::numeric_limits<int64_t>::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 00000000..3cfe34cf --- /dev/null +++ b/src/test/librbd/object_map/test_mock_ResizeRequest.cc @@ -0,0 +1,146 @@ +// -*- 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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new ResizeRequest( + *ictx, &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 00000000..6aa8d142 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotCreateRequest.cc @@ -0,0 +1,224 @@ +// -*- 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) { + RWLock::WLocker snap_locker(ictx->snap_lock); + RWLock::RLocker parent_locker(ictx->parent_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::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, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_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::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, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, 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::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, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, 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::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, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, 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::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, snap_id, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_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 00000000..b9dd8168 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotRemoveRequest.cc @@ -0,0 +1,330 @@ +// -*- 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::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + request->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + { + // shouldn't invalidate the HEAD revision when we fail to load + // the already deleted snapshot + RWLock::RLocker snap_locker(ictx->snap_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::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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::BitVector<2> object_map; + C_SaferCond cond_ctx; + AsyncRequest<> *request = new SnapshotRemoveRequest( + *ictx, &object_map, snap_id, &cond_ctx); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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::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(*ictx, ictx->snap_id); + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_lock); + om.set_object_map(object_map); + om.aio_save(&cond_ctx1); + } + 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, snap_id, &cond_ctx2); + { + RWLock::RLocker owner_locker(ictx->owner_lock); + RWLock::WLocker snap_locker(ictx->snap_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 00000000..19d89d10 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_SnapshotRollbackRequest.cc @@ -0,0 +1,147 @@ +// -*- 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()); + + { + RWLock::RLocker snap_locker(ictx->snap_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()); + + { + RWLock::RLocker snap_locker(ictx->snap_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 00000000..95879c88 --- /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<MockImageCtx> 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 00000000..b7de4d18 --- /dev/null +++ b/src/test/librbd/object_map/test_mock_UpdateRequest.cc @@ -0,0 +1,280 @@ +// -*- 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<uint8_t>& 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::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, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, snap_id, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->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<uint8_t>(), 0); + expect_unlock_exclusive_lock(*ictx); + + ceph::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, snap_id, 0, object_map.size(), OBJECT_EXISTS_CLEAN, + boost::optional<uint8_t>(), {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->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)); + + 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); + expect_unlock_exclusive_lock(*ictx); + + ceph::BitVector<2> object_map; + object_map.resize(712312); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, false, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->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::BitVector<2> object_map; + object_map.resize(1); + + C_SaferCond cond_ctx; + AsyncRequest<> *req = new UpdateRequest<>( + *ictx, &object_map, CEPH_NOSNAP, 0, object_map.size(), OBJECT_NONEXISTENT, + OBJECT_EXISTS, {}, true, &cond_ctx); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::WLocker object_map_locker(ictx->object_map_lock); + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); + + expect_unlock_exclusive_lock(*ictx); +} + +} // namespace object_map +} // namespace librbd |