// -*- 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 "librbd/ObjectMap.h" #include "librbd/object_map/RefreshRequest.h" #include "librbd/object_map/UnlockRequest.h" #include "librbd/object_map/UpdateRequest.h" namespace librbd { namespace { struct MockTestImageCtx : public MockImageCtx { MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { } }; } // anonymous namespace namespace object_map { template <> struct RefreshRequest { Context *on_finish = nullptr; ceph::BitVector<2u> *object_map = nullptr; static RefreshRequest *s_instance; static RefreshRequest *create(MockTestImageCtx &image_ctx, ceph::BitVector<2u> *object_map, uint64_t snap_id, Context *on_finish) { ceph_assert(s_instance != nullptr); s_instance->on_finish = on_finish; s_instance->object_map = object_map; return s_instance; } MOCK_METHOD0(send, void()); RefreshRequest() { s_instance = this; } }; template <> struct UnlockRequest { Context *on_finish = nullptr; static UnlockRequest *s_instance; static UnlockRequest *create(MockTestImageCtx &image_ctx, Context *on_finish) { ceph_assert(s_instance != nullptr); s_instance->on_finish = on_finish; return s_instance; } MOCK_METHOD0(send, void()); UnlockRequest() { s_instance = this; } }; template <> struct UpdateRequest { Context *on_finish = nullptr; static UpdateRequest *s_instance; static UpdateRequest *create(MockTestImageCtx &image_ctx, ceph::BitVector<2u> *object_map, uint64_t snap_id, uint64_t start_object_no, uint64_t end_object_no, uint8_t new_state, const boost::optional ¤t_state, const ZTracer::Trace &parent_trace, bool ignore_enoent, Context *on_finish) { ceph_assert(s_instance != nullptr); s_instance->on_finish = on_finish; s_instance->construct(snap_id, start_object_no, end_object_no, new_state, current_state, ignore_enoent); return s_instance; } MOCK_METHOD6(construct, void(uint64_t snap_id, uint64_t start_object_no, uint64_t end_object_no, uint8_t new_state, const boost::optional ¤t_state, bool ignore_enoent)); MOCK_METHOD0(send, void()); UpdateRequest() { s_instance = this; } }; RefreshRequest *RefreshRequest::s_instance = nullptr; UnlockRequest *UnlockRequest::s_instance = nullptr; UpdateRequest *UpdateRequest::s_instance = nullptr; } // namespace object_map } // namespace librbd #include "librbd/ObjectMap.cc" namespace librbd { using testing::_; using testing::InSequence; using testing::Invoke; class TestMockObjectMap : public TestMockFixture { public: typedef ObjectMap MockObjectMap; typedef object_map::RefreshRequest MockRefreshRequest; typedef object_map::UnlockRequest MockUnlockRequest; typedef object_map::UpdateRequest MockUpdateRequest; void expect_refresh(MockTestImageCtx &mock_image_ctx, MockRefreshRequest &mock_refresh_request, const ceph::BitVector<2u> &object_map, int r) { EXPECT_CALL(mock_refresh_request, send()) .WillOnce(Invoke([&mock_image_ctx, &mock_refresh_request, &object_map, r]() { *mock_refresh_request.object_map = object_map; mock_image_ctx.image_ctx->op_work_queue->queue(mock_refresh_request.on_finish, r); })); } void expect_unlock(MockTestImageCtx &mock_image_ctx, MockUnlockRequest &mock_unlock_request, int r) { EXPECT_CALL(mock_unlock_request, send()) .WillOnce(Invoke([&mock_image_ctx, &mock_unlock_request, r]() { mock_image_ctx.image_ctx->op_work_queue->queue(mock_unlock_request.on_finish, r); })); } void expect_update(MockTestImageCtx &mock_image_ctx, MockUpdateRequest &mock_update_request, uint64_t snap_id, uint64_t start_object_no, uint64_t end_object_no, uint8_t new_state, const boost::optional ¤t_state, bool ignore_enoent, Context **on_finish) { EXPECT_CALL(mock_update_request, construct(snap_id, start_object_no, end_object_no, new_state, current_state, ignore_enoent)) .Times(1); EXPECT_CALL(mock_update_request, send()) .WillOnce(Invoke([&mock_update_request, on_finish]() { *on_finish = mock_update_request.on_finish; })); } }; TEST_F(TestMockObjectMap, NonDetainedUpdate) { REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); librbd::ImageCtx *ictx; ASSERT_EQ(0, open_image(m_image_name, &ictx)); MockTestImageCtx mock_image_ctx(*ictx); InSequence seq; ceph::BitVector<2u> object_map; object_map.resize(4); MockRefreshRequest mock_refresh_request; expect_refresh(mock_image_ctx, mock_refresh_request, object_map, 0); MockUpdateRequest mock_update_request; Context *finish_update_1; expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, 0, 1, 1, {}, false, &finish_update_1); Context *finish_update_2; expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, 1, 2, 1, {}, false, &finish_update_2); MockUnlockRequest mock_unlock_request; expect_unlock(mock_image_ctx, mock_unlock_request, 0); MockObjectMap mock_object_map(mock_image_ctx, CEPH_NOSNAP); C_SaferCond open_ctx; mock_object_map.open(&open_ctx); ASSERT_EQ(0, open_ctx.wait()); C_SaferCond update_ctx1; C_SaferCond update_ctx2; { RWLock::RLocker snap_locker(mock_image_ctx.snap_lock); RWLock::WLocker object_map_locker(mock_image_ctx.object_map_lock); mock_object_map.aio_update(CEPH_NOSNAP, 0, 1, {}, {}, false, &update_ctx1); mock_object_map.aio_update(CEPH_NOSNAP, 1, 1, {}, {}, false, &update_ctx2); } finish_update_2->complete(0); ASSERT_EQ(0, update_ctx2.wait()); finish_update_1->complete(0); ASSERT_EQ(0, update_ctx1.wait()); C_SaferCond close_ctx; mock_object_map.close(&close_ctx); ASSERT_EQ(0, close_ctx.wait()); } TEST_F(TestMockObjectMap, DetainedUpdate) { REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); librbd::ImageCtx *ictx; ASSERT_EQ(0, open_image(m_image_name, &ictx)); MockTestImageCtx mock_image_ctx(*ictx); InSequence seq; ceph::BitVector<2u> object_map; object_map.resize(4); MockRefreshRequest mock_refresh_request; expect_refresh(mock_image_ctx, mock_refresh_request, object_map, 0); MockUpdateRequest mock_update_request; Context *finish_update_1; expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, 1, 4, 1, {}, false, &finish_update_1); Context *finish_update_2 = nullptr; expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, 1, 3, 1, {}, false, &finish_update_2); Context *finish_update_3 = nullptr; expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, 2, 3, 1, {}, false, &finish_update_3); Context *finish_update_4 = nullptr; expect_update(mock_image_ctx, mock_update_request, CEPH_NOSNAP, 0, 2, 1, {}, false, &finish_update_4); MockUnlockRequest mock_unlock_request; expect_unlock(mock_image_ctx, mock_unlock_request, 0); MockObjectMap mock_object_map(mock_image_ctx, CEPH_NOSNAP); C_SaferCond open_ctx; mock_object_map.open(&open_ctx); ASSERT_EQ(0, open_ctx.wait()); C_SaferCond update_ctx1; C_SaferCond update_ctx2; C_SaferCond update_ctx3; C_SaferCond update_ctx4; { RWLock::RLocker snap_locker(mock_image_ctx.snap_lock); RWLock::WLocker object_map_locker(mock_image_ctx.object_map_lock); mock_object_map.aio_update(CEPH_NOSNAP, 1, 4, 1, {}, {}, false, &update_ctx1); mock_object_map.aio_update(CEPH_NOSNAP, 1, 3, 1, {}, {}, false, &update_ctx2); mock_object_map.aio_update(CEPH_NOSNAP, 2, 3, 1, {}, {}, false, &update_ctx3); mock_object_map.aio_update(CEPH_NOSNAP, 0, 2, 1, {}, {}, false, &update_ctx4); } // updates 2, 3, and 4 are blocked on update 1 ASSERT_EQ(nullptr, finish_update_2); finish_update_1->complete(0); ASSERT_EQ(0, update_ctx1.wait()); // updates 3 and 4 are blocked on update 2 ASSERT_NE(nullptr, finish_update_2); ASSERT_EQ(nullptr, finish_update_3); ASSERT_EQ(nullptr, finish_update_4); finish_update_2->complete(0); ASSERT_EQ(0, update_ctx2.wait()); ASSERT_NE(nullptr, finish_update_3); ASSERT_NE(nullptr, finish_update_4); finish_update_3->complete(0); finish_update_4->complete(0); ASSERT_EQ(0, update_ctx3.wait()); ASSERT_EQ(0, update_ctx4.wait()); C_SaferCond close_ctx; mock_object_map.close(&close_ctx); ASSERT_EQ(0, close_ctx.wait()); } } // namespace librbd