#include #include #include #include #include #include #include #include #include #include #include #include #include "common/kv/IKVEngine.h" #include "common/kv/mem/MemKVEngine.h" #include "common/utils/Coroutine.h" #include "common/utils/UtcTime.h" #include "fbs/mgmtd/ChainRef.h" #include "fbs/mgmtd/MgmtdTypes.h" #include "meta/store/Inode.h" #include "meta/store/Utils.h" #include "tests/meta/MetaTestBase.h" namespace hf3fs::meta::server { using flat::ChainId; using flat::ChainRef; using flat::ChainTableId; using flat::ChainTableVersion; TEST(TestInodeId, Special) { auto root = InodeId::root(); auto gcRoot = InodeId::gcRoot(); ASSERT_NE(root, gcRoot); ASSERT_EQ(root.u64(), 0); ASSERT_EQ(gcRoot.u64(), 1); ASSERT_EQ(root, InodeId::root()); ASSERT_EQ(gcRoot, InodeId::gcRoot()); ASSERT_EQ(fmt::format("{}", root), "0x0000000000000000"); ASSERT_EQ(fmt::format("{}", gcRoot), "0x0000000000000001"); ASSERT_EQ(root.toHexString(), "0x0000000000000000"); ASSERT_EQ(gcRoot.toHexString(), "0x0000000000000001"); } template class TestInode : public MetaTestBase {}; using KVTypes = ::testing::Types; TYPED_TEST_SUITE(TestInode, KVTypes); TYPED_TEST(TestInode, File) { Inode inode = Inode::newFile(MetaTestHelper::randomInodeId(), Acl(Uid(0), Gid(0), Permission(0644)), Layout(), UtcClock::now().castGranularity(1_s)); ASSERT_TRUE(inode.isFile()); ASSERT_FALSE(inode.isDirectory()); ASSERT_FALSE(inode.isSymlink()); ASSERT_EQ(inode.getType(), InodeType::File); ASSERT_NO_THROW(inode.asFile()); } TYPED_TEST(TestInode, FileLayout) { auto layout = Layout::newEmpty(ChainTableId(1), 1000, 4); ASSERT_TRUE(std::holds_alternative(layout.chains)); ASSERT_FALSE(layout.valid(true)); layout = Layout::newEmpty(ChainTableId(1), 1024, 4); ASSERT_TRUE(layout.valid(true)); ASSERT_FALSE(layout.valid(false)); ASSERT_EQ(layout.getChainIndexList().size(), 0); layout.chains = Layout::ChainList{{1}}; ASSERT_FALSE(layout.valid(false)); layout.chains = Layout::ChainList{{1, 2, 3, 4}}; ASSERT_FALSE(layout.valid(false)) << fmt::format("{}", layout); layout.tableVersion = ChainTableVersion(1); ASSERT_TRUE(layout.valid(false)) << fmt::format("{}", layout); ASSERT_TRUE(std::holds_alternative(layout.chains)); ASSERT_EQ(layout.getChainIndexList().size(), layout.stripeSize); layout.chains = Layout::Empty(); layout.chains = Layout::ChainRange(1, Layout::ChainRange::STD_SHUFFLE_MT19937, 0); ASSERT_TRUE(layout.valid(false)); ASSERT_TRUE(std::holds_alternative(layout.chains)); ASSERT_EQ(layout.getChainIndexList().size(), layout.stripeSize); std::set set; for (auto chain : layout.getChainIndexList()) { ASSERT_FALSE(set.contains(chain)); set.emplace(chain); } } TYPED_TEST(TestInode, ChunkId) { Inode inode = Inode::newFile(MetaTestHelper::randomInodeId(), Acl(Uid(0), Gid(0), Permission(0644)), Layout(), UtcClock::now().castGranularity(1_s)); ASSERT_FALSE(inode.asFile().getChunkId(inode.id, 0)); inode.asFile().layout = Layout::newEmpty(ChainTableId(1), 4096, 1); ASSERT_TRUE(inode.asFile().getChunkId(inode.id, 0)); ASSERT_EQ(*inode.asFile().getChunkId(inode.id, 100), ChunkId(inode.id, 0, 0)); ASSERT_EQ(*inode.asFile().getChunkId(inode.id, 9999), ChunkId(inode.id, 0, 2)); ASSERT_EQ(inode.asFile().getChunkId(inode.id, 1ul << 63).error().code(), MetaCode::kFileTooLarge); for (int i = 0; i < 10; i++) { auto inode = meta::InodeId(folly::Random::rand64()); auto chunk = folly::Random::rand32(); ChunkId chunkId(inode, 0, chunk); ASSERT_EQ(chunkId.chunk(), chunk); ASSERT_EQ(chunkId.inode(), inode); auto packed = chunkId.pack(); ASSERT_EQ(chunkId, ChunkId::unpack(packed)); } } TYPED_TEST(TestInode, SerializedSize) { auto layout = Layout::newEmpty(ChainTableId(1), 4096, 1); layout.chains = Layout::ChainRange(5, Layout::ChainRange::NO_SHUFFLE, 0); auto file = Inode::newFile(MetaTestHelper::randomInodeId(), Acl(Uid(0), Gid(0), Permission(0644)), layout, UtcClock::now().castGranularity(1_s)); file.asFile().length = 100_MB; fmt::print("File with range layout, serialized {} bytes, data {} type {}, asFile {}.\n", serde::serialize(file).size(), serde::serialize(file.data()).size(), serde::serialize(file.data().type).size(), serde::serialize(file.data().asFile()).size()); auto directory = Inode::newDirectory(MetaTestHelper::randomInodeId(), MetaTestHelper::randomInodeId(), "directory-name", Acl(Uid(0), Gid(0), Permission(0644)), layout, UtcClock::now().castGranularity(1_s)); fmt::print("Directory with layout, serialized {} {} bytes.\n", serde::serialize(directory.data()).size(), serde::serialize(directory).size()); } TYPED_TEST(TestInode, Directory) { Inode inode = Inode::newDirectory(MetaTestHelper::randomInodeId(), MetaTestHelper::randomInodeId(), "directory", Acl(Uid(0), Gid(0), Permission(0644)), Layout::newEmpty(ChainTableId(1), 512 << 10, 64), UtcClock::now().castGranularity(1_s)); ASSERT_FALSE(inode.isFile()); ASSERT_TRUE(inode.isDirectory()); ASSERT_FALSE(inode.isSymlink()); ASSERT_EQ(inode.getType(), InodeType::Directory); ASSERT_NO_THROW(inode.asDirectory()); } TYPED_TEST(TestInode, Symlink) { Inode inode = Inode::newSymlink(MetaTestHelper::randomInodeId(), std::to_string(folly::Random::rand32()), Uid(0), Gid(0), UtcClock::now().castGranularity(1_s)); ASSERT_FALSE(inode.isFile()); ASSERT_FALSE(inode.isDirectory()); ASSERT_TRUE(inode.isSymlink()); ASSERT_EQ(inode.getType(), InodeType::Symlink); ASSERT_NO_THROW(inode.asSymlink()); } TYPED_TEST(TestInode, LoadStore) { folly::coro::blockingWait([&]() -> CoTask { static constexpr size_t kInodes = 1000; std::vector inodes; for (size_t i = 0; i < kInodes; i++) { auto inode = MetaTestHelper::randomInode(); READ_WRITE_TRANSACTION_OK({ auto storeResult = co_await inode.store(*txn); CO_ASSERT_OK(storeResult); }); inodes.push_back(inode); } for (auto &inode : inodes) { READ_ONLY_TRANSACTION({ auto loadResult = (co_await Inode::snapshotLoad(*txn, inode.id)).then(checkMetaFound); CO_ASSERT_OK(loadResult); auto other = std::move(loadResult.value()); CO_ASSERT_EQ(inode, other) << inode.isFile(); if (inode.isFile()) { auto cnts = inode.asFile().layout.getChainIndexList().size(); CO_ASSERT_EQ(cnts, other.asFile().layout.getChainIndexList().size()); for (size_t i = 0; i < cnts; i++) { CO_ASSERT_EQ(inode.asFile().layout.getChainIndexList()[i], other.asFile().layout.getChainIndexList()[i]); } } }); } for (auto &inode : inodes) { READ_WRITE_TRANSACTION_OK({ auto removeResult = co_await inode.remove(*txn); CO_ASSERT_OK(removeResult); }); } for (auto &inode : inodes) { READ_ONLY_TRANSACTION({ auto loadResult = (co_await Inode::snapshotLoad(*txn, inode.id)).then(checkMetaFound); CO_ASSERT_ERROR(loadResult, MetaCode::kNotFound); }); } }()); } TYPED_TEST(TestInode, Conflict) { folly::coro::blockingWait([&]() -> CoTask { for (int i = 0; i < 1000; i++) { // txn1 update inode, txn2 include inode into it's read conflict set (by load or addReadConflict) and set a random // key, txn1 commit first, txn2 should fail Inode inode = MetaTestHelper::randomInode(); CO_ASSERT_CONFLICT([&](auto &txn) -> CoTask { CO_ASSERT_OK(co_await inode.store(txn)); }, [&](auto &txn) -> CoTask { if (folly::Random::rand32() % 2) { CO_ASSERT_OK(co_await Inode::load(txn, inode.id)); } else { // use snapshotLoad to get a read version CO_ASSERT_OK(co_await Inode::snapshotLoad(txn, inode.id)); CO_ASSERT_OK(co_await Inode(inode.id).addIntoReadConflict(txn)); } auto set = co_await txn.set(std::to_string(folly::Random::rand32()), std::to_string(folly::Random::rand32())); CO_ASSERT_OK(set); }); } }()); } } // namespace hf3fs::meta::server