From e6918187568dbd01842d8d1d2c808ce16a894239 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:54:28 +0200 Subject: Adding upstream version 18.2.2. Signed-off-by: Daniel Baumann --- src/test/librbd/operation/test_mock_TrimRequest.cc | 493 +++++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 src/test/librbd/operation/test_mock_TrimRequest.cc (limited to 'src/test/librbd/operation/test_mock_TrimRequest.cc') diff --git a/src/test/librbd/operation/test_mock_TrimRequest.cc b/src/test/librbd/operation/test_mock_TrimRequest.cc new file mode 100644 index 000000000..1771e7413 --- /dev/null +++ b/src/test/librbd/operation/test_mock_TrimRequest.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/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/io/MockObjectDispatch.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "common/bit_vector.hpp" +#include "librbd/AsyncRequest.h" +#include "librbd/internal.h" +#include "librbd/ObjectMap.h" +#include "librbd/Utils.h" +#include "librbd/operation/TrimRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +template<> +struct AsyncRequest { + librbd::MockTestImageCtx& m_image_ctx; + Context *on_finish; + + AsyncRequest(librbd::MockTestImageCtx& image_ctx, Context* on_finish) + : m_image_ctx(image_ctx), on_finish(on_finish) { + } + virtual ~AsyncRequest() { + } + + Context* create_callback_context() { + return util::create_context_callback(this); + } + + Context* create_async_callback_context() { + return util::create_context_callback(this); + } + + void complete(int r) { + if (should_complete(r)) { + async_complete(r); + } + } + + void async_complete(int r) { + on_finish->complete(r); + delete this; + } + + bool is_canceled() const { + return false; + } + + virtual void send() = 0; + virtual bool should_complete(int r) = 0; +}; + +namespace io { + +struct DiscardVisitor + : public boost::static_visitor { + ObjectDispatchSpec::DiscardRequest* + operator()(ObjectDispatchSpec::DiscardRequest& discard) const { + return &discard; + } + + template + ObjectDispatchSpec::DiscardRequest* + operator()(T& t) const { + return nullptr; + } +}; + +} // namespace io +} // namespace librbd + +// template definitions +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/operation/TrimRequest.cc" + +namespace librbd { +namespace operation { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockOperationTrimRequest : public TestMockFixture { +public: + typedef TrimRequest MockTrimRequest; + + int create_snapshot(const char *snap_name) { + librbd::ImageCtx *ictx; + int r = open_image(m_image_name, &ictx); + if (r < 0) { + return r; + } + + r = snap_create(*ictx, snap_name); + if (r < 0) { + return r; + } + + r = snap_protect(*ictx, snap_name); + if (r < 0) { + return r; + } + close_image(ictx); + return 0; + } + + void expect_is_lock_owner(MockTestImageCtx &mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, is_lock_owner()) + .WillRepeatedly(Return(true)); + } + } + + void expect_object_map_update(MockTestImageCtx &mock_image_ctx, + uint64_t start_object, uint64_t end_object, + uint8_t state, uint8_t current_state, + bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(CEPH_NOSNAP, start_object, end_object, state, + boost::optional(current_state), _, false, _)) + .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) { + if (updated) { + mock_image_ctx.op_work_queue->queue(ctx, ret_val); + } + return updated; + }))); + } + } + + void expect_get_parent_overlap(MockTestImageCtx &mock_image_ctx, + uint64_t overlap) { + EXPECT_CALL(mock_image_ctx, get_parent_overlap(CEPH_NOSNAP, _)) + .WillOnce(WithArg<1>(Invoke([overlap](uint64_t *o) { + *o = overlap; + return 0; + }))); + } + + void expect_reduce_parent_overlap(MockTestImageCtx& mock_image_ctx, + uint64_t overlap) { + EXPECT_CALL(mock_image_ctx, reduce_parent_overlap(overlap, false)) + .WillOnce(Return(std::make_pair(overlap, io::ImageArea::DATA))); + } + + void expect_get_area_size(MockTestImageCtx& mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_area_size(io::ImageArea::CRYPTO_HEADER)) + .WillOnce(Return(0)); + } + + void expect_object_may_exist(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, bool exists) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, object_may_exist(object_no)) + .WillOnce(Return(exists)); + } + } + + void expect_get_object_name(MockTestImageCtx &mock_image_ctx, + uint64_t object_no, const std::string& oid) { + EXPECT_CALL(mock_image_ctx, get_object_name(object_no)) + .WillOnce(Return(oid)); + } + + void expect_aio_remove(MockTestImageCtx &mock_image_ctx, + const std::string& oid, int ret_val) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), remove(oid, _)) + .WillOnce(Return(ret_val)); + } + + void expect_object_discard(MockImageCtx &mock_image_ctx, + io::MockObjectDispatch& mock_io_object_dispatch, + uint64_t offset, uint64_t length, + bool update_object_map, int r) { + EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_)) + .WillOnce(Invoke([&mock_image_ctx, offset, length, update_object_map, r] + (io::ObjectDispatchSpec* spec) { + auto discard = boost::apply_visitor(io::DiscardVisitor(), spec->request); + ASSERT_TRUE(discard != nullptr); + ASSERT_EQ(offset, discard->object_off); + ASSERT_EQ(length, discard->object_len); + int flags = 0; + if (!update_object_map) { + flags = io::OBJECT_DISCARD_FLAG_DISABLE_OBJECT_MAP_UPDATE; + } + ASSERT_EQ(flags, discard->discard_flags); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + mock_image_ctx.op_work_queue->queue(&spec->dispatcher_ctx, r); + })); + } +}; + +TEST_F(TestMockOperationTrimRequest, SuccessRemove) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, OBJECT_EXISTS, + true, 0); + + // copy-up + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, 0); + expect_reduce_parent_overlap(mock_image_ctx, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_aio_remove(mock_image_ctx, "object0", 0); + + // post + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_NONEXISTENT, + OBJECT_PENDING, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, m_image_size, 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessCopyUp) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING) + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_PENDING, OBJECT_EXISTS, + true, 0); + + // copy-up + io::MockObjectDispatch mock_io_object_dispatch; + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_reduce_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 0, + ictx->get_object_size(), false, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 1, true); + expect_get_object_name(mock_image_ctx, 1, "object1"); + expect_aio_remove(mock_image_ctx, "object1", 0); + + // post + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_NONEXISTENT, + OBJECT_PENDING, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, 2 * ictx->get_object_size(), 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessBoundary) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // boundary + io::MockObjectDispatch mock_io_object_dispatch; + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 1, + ictx->get_object_size() - 1, true, 0); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, ictx->get_object_size(), 1, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(0, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, SuccessNoOp) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); +} + +TEST_F(TestMockOperationTrimRequest, RemoveError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, OBJECT_EXISTS, + false, 0); + + // copy-up + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, 0); + expect_reduce_parent_overlap(mock_image_ctx, 0); + + // remove + expect_object_may_exist(mock_image_ctx, 0, true); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_aio_remove(mock_image_ctx, "object0", -EPERM); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, m_image_size, 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EPERM, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, CopyUpError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING) + ASSERT_EQ(0, create_snapshot("snap1")); + + int order = 22; + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + std::string clone_name = get_temp_image_name(); + ASSERT_EQ(0, librbd::clone(m_ioctx, m_image_name.c_str(), "snap1", m_ioctx, + clone_name.c_str(), features, &order, 0, 0)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + ASSERT_EQ(0, snap_create(*ictx, "snap")); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // pre + expect_object_map_update(mock_image_ctx, 0, 2, OBJECT_PENDING, OBJECT_EXISTS, + false, 0); + + // copy-up + io::MockObjectDispatch mock_io_object_dispatch; + expect_get_area_size(mock_image_ctx); + expect_get_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_reduce_parent_overlap(mock_image_ctx, ictx->get_object_size()); + expect_get_object_name(mock_image_ctx, 0, "object0"); + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 0, + ictx->get_object_size(), false, -EINVAL); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, 2 * ictx->get_object_size(), 0, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +TEST_F(TestMockOperationTrimRequest, BoundaryError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + EXPECT_CALL(mock_image_ctx, get_stripe_period()).WillOnce(Return(ictx->get_object_size())); + EXPECT_CALL(mock_image_ctx, get_stripe_count()).WillOnce(Return(ictx->get_stripe_count())); + + // boundary + io::MockObjectDispatch mock_io_object_dispatch; + expect_object_discard(mock_image_ctx, mock_io_object_dispatch, 1, + ictx->get_object_size() - 1, true, -EINVAL); + + C_SaferCond cond_ctx; + librbd::NoOpProgressContext progress_ctx; + MockTrimRequest *req = new MockTrimRequest( + mock_image_ctx, &cond_ctx, ictx->get_object_size(), 1, progress_ctx); + { + std::shared_lock owner_locker{mock_image_ctx.owner_lock}; + req->send(); + } + ASSERT_EQ(-EINVAL, cond_ctx.wait()); +} + +} // namespace operation +} // namespace librbd -- cgit v1.2.3