// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "test/crimson/gtest_seastar.h" #include "test/crimson/seastore/transaction_manager_test_state.h" #include "crimson/os/seastore/onode.h" #include "crimson/os/seastore/object_data_handler.h" using namespace crimson; using namespace crimson::os; using namespace crimson::os::seastore; #define MAX_OBJECT_SIZE (16<<20) #define DEFAULT_OBJECT_DATA_RESERVATION (16<<20) #define DEFAULT_OBJECT_METADATA_RESERVATION (16<<20) namespace { [[maybe_unused]] seastar::logger& logger() { return crimson::get_logger(ceph_subsys_test); } } class TestOnode final : public Onode { onode_layout_t layout; bool dirty = false; public: TestOnode(uint32_t ddr, uint32_t dmr) : Onode(ddr, dmr) {} const onode_layout_t &get_layout() const final { return layout; } onode_layout_t &get_mutable_layout(Transaction &t) final { dirty = true; return layout; } bool is_alive() const { return true; } bool is_dirty() const { return dirty; } laddr_t get_hint() const final {return L_ADDR_MIN; } ~TestOnode() final = default; }; struct object_data_handler_test_t: public seastar_test_suite_t, TMTestState { OnodeRef onode; bufferptr known_contents; extent_len_t size = 0; object_data_handler_test_t() {} void write(Transaction &t, objaddr_t offset, extent_len_t len, char fill) { ceph_assert(offset + len <= known_contents.length()); size = std::max(size, offset + len); memset( known_contents.c_str() + offset, fill, len); bufferlist bl; bl.append( bufferptr( known_contents, offset, len)); with_trans_intr(t, [&](auto &t) { return ObjectDataHandler(MAX_OBJECT_SIZE).write( ObjectDataHandler::context_t{ *tm, t, *onode, }, offset, bl); }).unsafe_get0(); } void write(objaddr_t offset, extent_len_t len, char fill) { auto t = create_mutate_transaction(); write(*t, offset, len, fill); return submit_transaction(std::move(t)); } void truncate(Transaction &t, objaddr_t offset) { if (size > offset) { memset( known_contents.c_str() + offset, 0, size - offset); with_trans_intr(t, [&](auto &t) { return ObjectDataHandler(MAX_OBJECT_SIZE).truncate( ObjectDataHandler::context_t{ *tm, t, *onode }, offset); }).unsafe_get0(); } size = offset; } void truncate(objaddr_t offset) { auto t = create_mutate_transaction(); truncate(*t, offset); return submit_transaction(std::move(t)); } void read(Transaction &t, objaddr_t offset, extent_len_t len) { bufferlist bl = with_trans_intr(t, [&](auto &t) { return ObjectDataHandler(MAX_OBJECT_SIZE).read( ObjectDataHandler::context_t{ *tm, t, *onode }, offset, len); }).unsafe_get0(); bufferlist known; known.append( bufferptr( known_contents, offset, len)); EXPECT_EQ(bl.length(), known.length()); EXPECT_EQ(bl, known); } void read(objaddr_t offset, extent_len_t len) { auto t = create_read_transaction(); read(*t, offset, len); } void read_near(objaddr_t offset, extent_len_t len, extent_len_t fuzz) { auto fuzzes = std::vector{-1 * (int32_t)fuzz, 0, (int32_t)fuzz}; for (auto left_fuzz : fuzzes) { for (auto right_fuzz : fuzzes) { read(offset + left_fuzz, len - left_fuzz + right_fuzz); } } } std::list get_mappings(objaddr_t offset, extent_len_t length) { auto t = create_mutate_transaction(); auto ret = with_trans_intr(*t, [&](auto &t) { return tm->get_pins(t, offset, length); }).unsafe_get0(); return ret; } seastar::future<> set_up_fut() final { onode = new TestOnode( DEFAULT_OBJECT_DATA_RESERVATION, DEFAULT_OBJECT_METADATA_RESERVATION); known_contents = buffer::create(4<<20 /* 4MB */); memset(known_contents.c_str(), 0, known_contents.length()); size = 0; return tm_setup(); } seastar::future<> tear_down_fut() final { onode.reset(); size = 0; return tm_teardown(); } }; TEST_P(object_data_handler_test_t, single_write) { run_async([this] { write(1<<20, 8<<10, 'c'); read_near(1<<20, 8<<10, 1); read_near(1<<20, 8<<10, 512); }); } TEST_P(object_data_handler_test_t, multi_write) { run_async([this] { write((1<<20) - (4<<10), 4<<10, 'a'); write(1<<20, 4<<10, 'b'); write((1<<20) + (4<<10), 4<<10, 'c'); read_near(1<<20, 4<<10, 1); read_near(1<<20, 4<<10, 512); read_near((1<<20)-(4<<10), 12<<10, 1); read_near((1<<20)-(4<<10), 12<<10, 512); }); } TEST_P(object_data_handler_test_t, write_hole) { run_async([this] { write((1<<20) - (4<<10), 4<<10, 'a'); // hole at 1<<20 write((1<<20) + (4<<10), 4<<10, 'c'); read_near(1<<20, 4<<10, 1); read_near(1<<20, 4<<10, 512); read_near((1<<20)-(4<<10), 12<<10, 1); read_near((1<<20)-(4<<10), 12<<10, 512); }); } TEST_P(object_data_handler_test_t, overwrite_single) { run_async([this] { write((1<<20), 4<<10, 'a'); write((1<<20), 4<<10, 'c'); read_near(1<<20, 4<<10, 1); read_near(1<<20, 4<<10, 512); }); } TEST_P(object_data_handler_test_t, overwrite_double) { run_async([this] { write((1<<20), 4<<10, 'a'); write((1<<20)+(4<<10), 4<<10, 'c'); write((1<<20), 8<<10, 'b'); read_near(1<<20, 8<<10, 1); read_near(1<<20, 8<<10, 512); read_near(1<<20, 4<<10, 1); read_near(1<<20, 4<<10, 512); read_near((1<<20) + (4<<10), 4<<10, 1); read_near((1<<20) + (4<<10), 4<<10, 512); }); } TEST_P(object_data_handler_test_t, overwrite_partial) { run_async([this] { write((1<<20), 12<<10, 'a'); read_near(1<<20, 12<<10, 1); write((1<<20)+(8<<10), 4<<10, 'b'); read_near(1<<20, 12<<10, 1); write((1<<20)+(4<<10), 4<<10, 'c'); read_near(1<<20, 12<<10, 1); write((1<<20), 4<<10, 'd'); read_near(1<<20, 12<<10, 1); read_near(1<<20, 12<<10, 512); read_near(1<<20, 4<<10, 1); read_near(1<<20, 4<<10, 512); read_near((1<<20) + (4<<10), 4<<10, 1); read_near((1<<20) + (4<<10), 4<<10, 512); }); } TEST_P(object_data_handler_test_t, unaligned_write) { run_async([this] { objaddr_t base = 1<<20; write(base, (4<<10)+(1<<10), 'a'); read_near(base-(4<<10), 12<<10, 512); base = (1<<20) + (64<<10); write(base+(1<<10), (4<<10)+(1<<10), 'b'); read_near(base-(4<<10), 12<<10, 512); base = (1<<20) + (128<<10); write(base-(1<<10), (4<<10)+(2<<20), 'c'); read_near(base-(4<<10), 12<<10, 512); }); } TEST_P(object_data_handler_test_t, unaligned_overwrite) { run_async([this] { objaddr_t base = 1<<20; write(base, (128<<10) + (16<<10), 'x'); write(base, (4<<10)+(1<<10), 'a'); read_near(base-(4<<10), 12<<10, 2<<10); base = (1<<20) + (64<<10); write(base+(1<<10), (4<<10)+(1<<10), 'b'); read_near(base-(4<<10), 12<<10, 2<<10); base = (1<<20) + (128<<10); write(base-(1<<10), (4<<10)+(2<<20), 'c'); read_near(base-(4<<10), 12<<10, 2<<10); read(base, (128<<10) + (16<<10)); }); } TEST_P(object_data_handler_test_t, truncate) { run_async([this] { objaddr_t base = 1<<20; write(base, 8<<10, 'a'); write(base+(8<<10), 8<<10, 'b'); write(base+(16<<10), 8<<10, 'c'); truncate(base + (32<<10)); read(base, 64<<10); truncate(base + (24<<10)); read(base, 64<<10); truncate(base + (12<<10)); read(base, 64<<10); truncate(base - (12<<10)); read(base, 64<<10); }); } TEST_P(object_data_handler_test_t, no_split) { run_async([this] { write(0, 8<<10, 'x'); write(0, 8<<10, 'a'); auto pins = get_mappings(0, 8<<10); EXPECT_EQ(pins.size(), 1); read(0, 8<<10); }); } TEST_P(object_data_handler_test_t, split_left) { run_async([this] { write(0, 128<<10, 'x'); write(64<<10, 60<<10, 'a'); auto pins = get_mappings(0, 128<<10); EXPECT_EQ(pins.size(), 2); size_t res[2] = {0, 64<<10}; auto base = pins.front()->get_key(); int i = 0; for (auto &pin : pins) { EXPECT_EQ(pin->get_key() - base, res[i]); i++; } read(0, 128<<10); }); } TEST_P(object_data_handler_test_t, split_right) { run_async([this] { write(0, 128<<10, 'x'); write(4<<10, 60<<10, 'a'); auto pins = get_mappings(0, 128<<10); EXPECT_EQ(pins.size(), 2); size_t res[2] = {0, 64<<10}; auto base = pins.front()->get_key(); int i = 0; for (auto &pin : pins) { EXPECT_EQ(pin->get_key() - base, res[i]); i++; } read(0, 128<<10); }); } TEST_P(object_data_handler_test_t, split_left_right) { run_async([this] { write(0, 128<<10, 'x'); write(48<<10, 32<<10, 'a'); auto pins = get_mappings(0, 128<<10); EXPECT_EQ(pins.size(), 3); size_t res[3] = {0, 48<<10, 80<<10}; auto base = pins.front()->get_key(); int i = 0; for (auto &pin : pins) { EXPECT_EQ(pin->get_key() - base, res[i]); i++; } }); } TEST_P(object_data_handler_test_t, multiple_split) { run_async([this] { write(0, 128<<10, 'x'); auto t = create_mutate_transaction(); // normal split write(*t, 120<<10, 4<<10, 'a'); // not aligned right write(*t, 4<<10, 5<<10, 'b'); // split right extent of last split result write(*t, 32<<10, 4<<10, 'c'); // non aligned overwrite write(*t, 13<<10, 4<<10, 'd'); write(*t, 64<<10, 32<<10, 'e'); // not split right write(*t, 60<<10, 8<<10, 'f'); submit_transaction(std::move(t)); auto pins = get_mappings(0, 128<<10); EXPECT_EQ(pins.size(), 10); size_t res[10] = {0, 4<<10, 12<<10, 20<<10, 32<<10, 36<<10, 60<<10, 96<<10, 120<<10, 124<<10}; auto base = pins.front()->get_key(); int i = 0; for (auto &pin : pins) { EXPECT_EQ(pin->get_key() - base, res[i]); i++; } read(0, 128<<10); }); } INSTANTIATE_TEST_SUITE_P( object_data_handler_test, object_data_handler_test_t, ::testing::Values ( "segmented", "circularbounded" ) );