mirror of
https://github.com/deepseek-ai/3FS
synced 2025-06-26 18:16:45 +00:00
Initial commit
This commit is contained in:
80
tests/meta/components/TestAclCache.cc
Normal file
80
tests/meta/components/TestAclCache.cc
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <atomic>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "meta/components/AclCache.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
TEST(TestAclCache, basic) {
|
||||
std::map<InodeId, Acl> map;
|
||||
AclCache cache(1 << 20);
|
||||
for (size_t i = 0; i < (64 << 10); i++) {
|
||||
InodeId inode{folly::Random::rand64()};
|
||||
Acl acl{flat::Uid(folly::Random::rand32()),
|
||||
flat::Gid(folly::Random::rand32()),
|
||||
meta::Permission(folly::Random::rand32())};
|
||||
map[inode] = acl;
|
||||
cache.set(inode, acl);
|
||||
}
|
||||
|
||||
for (auto [inode, acl] : map) {
|
||||
ASSERT_EQ(cache.get(inode, 0_s), std::nullopt);
|
||||
ASSERT_EQ(cache.get(inode, 1_h), acl);
|
||||
cache.invalid(inode);
|
||||
ASSERT_EQ(cache.get(inode, 1_h), std::nullopt);
|
||||
Acl newacl{flat::Uid(folly::Random::rand32()),
|
||||
flat::Gid(folly::Random::rand32()),
|
||||
meta::Permission(folly::Random::rand32())};
|
||||
cache.set(inode, newacl);
|
||||
ASSERT_EQ(cache.get(inode, 1_h), newacl);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestAclCache, benchmark) {
|
||||
std::vector<InodeId> inodes;
|
||||
AclCache cache(4 << 20);
|
||||
for (size_t i = 0; i < (1 << 20); i++) {
|
||||
InodeId inode{folly::Random::rand64(1 << 20)};
|
||||
inodes.emplace_back(inode);
|
||||
cache.set(inode, Acl());
|
||||
}
|
||||
|
||||
std::atomic<size_t> total;
|
||||
std::vector<std::jthread> threads;
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
threads.emplace_back([&]() {
|
||||
size_t ops = 0;
|
||||
auto now = SteadyClock::now();
|
||||
while (SteadyClock::now() - now < 1_s) {
|
||||
auto inode = inodes.at(folly::Random::rand64(inodes.size()));
|
||||
if (folly::Random::oneIn(4)) {
|
||||
// set
|
||||
cache.set(inode, Acl());
|
||||
} else if (folly::Random::oneIn(4)) {
|
||||
cache.invalid(inode);
|
||||
} else {
|
||||
// get
|
||||
cache.get(inode, 1_h);
|
||||
}
|
||||
ops += 1;
|
||||
}
|
||||
total += ops;
|
||||
});
|
||||
}
|
||||
|
||||
for (auto &th : threads) {
|
||||
th.join();
|
||||
}
|
||||
|
||||
fmt::print("total {} ops in 1s\n", total.load());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
123
tests/meta/components/TestChainAllocator.cc
Normal file
123
tests/meta/components/TestChainAllocator.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest-param-test.h>
|
||||
#include <map>
|
||||
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/mgmtd/ChainRef.h"
|
||||
#include "fbs/mgmtd/MgmtdTypes.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/ChainAllocator.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
DEFINE_int64(chain_alloc_test, 50000, "files in test chain allocator");
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestChainAllocator : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestChainAllocator, KVTypes);
|
||||
|
||||
TYPED_TEST(TestChainAllocator, basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto mgmtd = cluster.mgmtdClient();
|
||||
ChainAllocator alloc(mgmtd);
|
||||
|
||||
auto empty = Layout::newEmpty(flat::ChainTableId(1), flat::ChainTableVersion(0), 512 << 10, 16);
|
||||
CO_ASSERT_OK(co_await alloc.checkLayoutValid(empty));
|
||||
CO_ASSERT_OK(co_await alloc.allocateChainsForLayout(empty));
|
||||
CO_ASSERT_TRUE(empty.valid(false));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestChainAllocator, perDirCounter) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto mgmtd = cluster.mgmtdClient();
|
||||
ChainAllocator alloc(mgmtd);
|
||||
|
||||
CO_ASSERT_OK(
|
||||
co_await meta.setAttr(SetAttrReq::setIFlags(SUPER_USER, InodeId::root(), IFlags(FS_CHAIN_ALLOCATION_FL))));
|
||||
|
||||
auto routing = mgmtd->getRoutingInfo();
|
||||
auto chainCount = routing->raw()->getChainTable(flat::ChainTableId(1), flat::ChainTableVersion(0))->chains.size();
|
||||
|
||||
std::optional<size_t> prevChain;
|
||||
for (size_t i = 0; i < 1000; i++) {
|
||||
auto result = co_await meta.create({SUPER_USER, folly::to<std::string>(i), std::nullopt, OpenFlags(), p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto layout = result->stat.asFile().layout;
|
||||
CO_ASSERT_OK(co_await alloc.checkLayoutValid(layout));
|
||||
auto chain = std::get<Layout::ChainRange>(layout.chains).baseIndex;
|
||||
if (prevChain) {
|
||||
CO_ASSERT_EQ(chain % chainCount, (*prevChain + layout.stripeSize) % chainCount);
|
||||
}
|
||||
prevChain = chain;
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestChainAllocator, balance) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto mgmtd = cluster.mgmtdClient();
|
||||
ChainAllocator alloc(mgmtd);
|
||||
|
||||
for (auto tableId :
|
||||
std::vector<flat::ChainTableId>{flat::ChainTableId(1), flat::ChainTableId(2), flat::ChainTableId(3)}) {
|
||||
auto routing = mgmtd->getRoutingInfo();
|
||||
auto chainCount = routing->raw()->getChainTable(tableId, flat::ChainTableVersion(0))->chains.size();
|
||||
|
||||
std::map<flat::ChainId, size_t> map;
|
||||
std::vector<size_t> stripeSizes{1, 2, 4, 8, 30, 32, 60, 64, 120, 128, 240, 256, 512};
|
||||
for (int i = 0; i < FLAGS_chain_alloc_test; i++) {
|
||||
auto stripe = 1000000u;
|
||||
while (stripe > chainCount) {
|
||||
stripe = stripeSizes.at(folly::Random::rand64(stripeSizes.size()));
|
||||
}
|
||||
auto empty = Layout::newEmpty(tableId, 512 << 10, stripe);
|
||||
auto emptyCheck = co_await alloc.checkLayoutValid(empty);
|
||||
CO_ASSERT_OK(emptyCheck);
|
||||
CO_ASSERT_OK(co_await alloc.allocateChainsForLayout(empty));
|
||||
CO_ASSERT_TRUE(empty.valid(false));
|
||||
CO_ASSERT_OK(co_await alloc.checkLayoutValid(empty));
|
||||
|
||||
size_t begin = chainCount;
|
||||
std::set<flat::ChainId> set;
|
||||
for (auto chain : empty.getChainIndexList()) {
|
||||
auto index = chain % chainCount ? chain % chainCount : chainCount;
|
||||
auto ref = flat::ChainRef(empty.tableId, empty.tableVersion, index);
|
||||
auto id = routing->raw()->getChainId(ref);
|
||||
CO_ASSERT_TRUE(id.has_value());
|
||||
CO_ASSERT_FALSE(set.contains(*id));
|
||||
set.emplace(*id);
|
||||
map[*id]++;
|
||||
begin = std::min(index, begin);
|
||||
}
|
||||
|
||||
if (chainCount % stripe == 0 && stripe != 1) {
|
||||
CO_ASSERT_EQ(begin % stripe, 1)
|
||||
<< fmt::format("{} {} {}", stripe, begin, fmt::join(set.begin(), set.end(), ","));
|
||||
}
|
||||
}
|
||||
|
||||
size_t min = map.begin()->second, max = map.begin()->second;
|
||||
for (auto [id, cnt] : map) {
|
||||
(void)id;
|
||||
min = std::min(cnt, min);
|
||||
max = std::max(cnt, max);
|
||||
}
|
||||
CO_ASSERT_EQ(map.size(), chainCount);
|
||||
CO_ASSERT_GE(min + stripeSizes.size(), max);
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
215
tests/meta/components/TestDistributor.cc
Normal file
215
tests/meta/components/TestDistributor.cc
Normal file
@@ -0,0 +1,215 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Invoke.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "common/app/NodeId.h"
|
||||
#include "common/kv/ITransaction.h"
|
||||
#include "common/utils/BackgroundRunner.h"
|
||||
#include "common/utils/CPUExecutorGroup.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Duration.h"
|
||||
#include "common/utils/MurmurHash3.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/Distributor.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
DEFINE_uint64(distributor_iters, 10, "");
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
#define CHECK_DIST(txn, dist, inodeId, expected) \
|
||||
do { \
|
||||
auto result = co_await dist.checkOnServer((txn), (inodeId), (expected)); \
|
||||
CO_ASSERT_OK(result); \
|
||||
auto actual = (dist).getServer((inodeId)); \
|
||||
auto msg = fmt::format("{} not on {}, actual {}", (inodeId), (expected), (actual)); \
|
||||
CO_ASSERT_TRUE(result->first) << msg; \
|
||||
CO_ASSERT_EQ((actual), (expected)) << msg; \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_DIST_AT(dist, inodeId, expected) \
|
||||
do { \
|
||||
READ_WRITE_TRANSACTION_OK({ CHECK_DIST(*txn, dist, inodeId, expected); }); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_ALL_DIST_AT(dist, expected) \
|
||||
do { \
|
||||
for (size_t i = 0; i < 10; i++) { \
|
||||
READ_WRITE_TRANSACTION_OK({ \
|
||||
for (size_t j = 0; j < 1; j++) { \
|
||||
auto inodeId = InodeId(folly::Random::rand64()); \
|
||||
CHECK_DIST(*txn, dist, inodeId, expected); \
|
||||
} \
|
||||
}); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
template <typename KV>
|
||||
class TestDistributor : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestDistributor, KVTypes);
|
||||
|
||||
TYPED_TEST(TestDistributor, basic) {
|
||||
auto config = Distributor::Config();
|
||||
config.set_update_interval(1_s);
|
||||
config.set_timeout(5_s);
|
||||
auto kvEngine = this->kvEngine();
|
||||
auto exec = CPUExecutorGroup(2, "test");
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto dist = Distributor(config, flat::NodeId(50), kvEngine);
|
||||
CHECK_ALL_DIST_AT(dist, flat::NodeId());
|
||||
dist.start(exec);
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
CHECK_ALL_DIST_AT(dist, flat::NodeId(50));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<IReadWriteTransaction>> txns;
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto txn = kvEngine->createReadWriteTransaction();
|
||||
auto result = co_await dist.checkOnServer(*txn, InodeId(0));
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_TRUE(result->first);
|
||||
auto dummy = std::string(8, '\0');
|
||||
CO_ASSERT_OK(co_await txn->clear(dummy));
|
||||
txns.push_back(std::move(txn));
|
||||
}
|
||||
|
||||
dist.stopAndJoin();
|
||||
for (auto &txn : txns) {
|
||||
CO_ASSERT_ERROR(co_await txn->commit(), TransactionCode::kConflict);
|
||||
}
|
||||
CHECK_ALL_DIST_AT(dist, flat::NodeId());
|
||||
}());
|
||||
|
||||
exec.join();
|
||||
}
|
||||
|
||||
TYPED_TEST(TestDistributor, balance) {
|
||||
std::map<flat::NodeId, size_t> cnts;
|
||||
std::map<InodeId, flat::NodeId> map;
|
||||
std::vector<flat::NodeId> nodes;
|
||||
|
||||
auto servers = folly::Random::rand32(3, 10);
|
||||
for (size_t i = 0; i < servers; i++) {
|
||||
auto nodeId = flat::NodeId(50 + i);
|
||||
nodes.push_back(nodeId);
|
||||
}
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
auto inodeId = InodeId(folly::Random::rand64());
|
||||
auto server = Weight::select(nodes, inodeId);
|
||||
cnts[server]++;
|
||||
map[inodeId] = server;
|
||||
}
|
||||
|
||||
auto max = std::numeric_limits<size_t>::min();
|
||||
auto min = std::numeric_limits<size_t>::max();
|
||||
for (auto &[node, cnt] : cnts) {
|
||||
max = std::max(max, cnt);
|
||||
min = std::min(min, cnt);
|
||||
}
|
||||
fmt::print("min {}, max {}, avg {}\n", min, max, map.size() / nodes.size());
|
||||
|
||||
nodes.erase(nodes.begin() + folly::Random::rand32(servers));
|
||||
|
||||
size_t changed = 0;
|
||||
for (auto &[inodeId, server] : map) {
|
||||
auto newServer = Weight::select(nodes, inodeId);
|
||||
if (server != newServer) {
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
fmt::print("changed {}, expected {}\n", changed, map.size() / (nodes.size() + 1));
|
||||
}
|
||||
|
||||
TYPED_TEST(TestDistributor, multiple) {
|
||||
auto config = Distributor::Config();
|
||||
config.set_update_interval(100_ms);
|
||||
config.set_timeout(1_s);
|
||||
auto kvEngine = this->kvEngine();
|
||||
auto exec = CPUExecutorGroup(4, "distributor");
|
||||
auto exec2 = CPUExecutorGroup(4, "worker");
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
std::map<flat::NodeId, std::unique_ptr<Distributor>> distMap;
|
||||
std::map<flat::NodeId, bool> activeMap;
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto nodeId = flat::NodeId(50 + i);
|
||||
distMap[nodeId] = std::make_unique<Distributor>(config, nodeId, kvEngine);
|
||||
activeMap[nodeId] = false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < FLAGS_distributor_iters; i++) {
|
||||
std::vector<flat::NodeId> activeNodes;
|
||||
for (auto &iter : activeMap) {
|
||||
auto nodeId = iter.first;
|
||||
auto &active = iter.second;
|
||||
auto &dist = distMap[nodeId];
|
||||
if (folly::Random::oneIn(2)) {
|
||||
if (active) {
|
||||
auto update = folly::Random::oneIn(2);
|
||||
XLOGF(CRITICAL, "stop {}, update {}", nodeId, update);
|
||||
dist->stopAndJoin(update);
|
||||
} else {
|
||||
XLOGF(CRITICAL, "start {}", nodeId);
|
||||
dist->start(exec);
|
||||
}
|
||||
active = !active;
|
||||
}
|
||||
if (active) {
|
||||
activeNodes.push_back(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeNodes.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(5_s);
|
||||
std::vector<folly::SemiFuture<Void>> tasks;
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
auto task = folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto inodeId = InodeId(folly::Random::rand64());
|
||||
auto expected = Weight::select(activeNodes, inodeId);
|
||||
for (auto &[nodeId, dist] : distMap) {
|
||||
READ_WRITE_TRANSACTION_OK({ CHECK_DIST(*txn, (*dist), inodeId, expected); });
|
||||
}
|
||||
co_await folly::coro::co_current_executor;
|
||||
}
|
||||
});
|
||||
tasks.push_back(std::move(task).scheduleOn(&exec2.pickNext()).start());
|
||||
}
|
||||
co_await folly::coro::collectAllRange(std::move(tasks));
|
||||
}
|
||||
}());
|
||||
|
||||
XLOGF(INFO, "test finished");
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
138
tests/meta/components/TestFileHelper.cc
Normal file
138
tests/meta/components/TestFileHelper.cc
Normal file
@@ -0,0 +1,138 @@
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Math.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Invoke.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/futures/Future.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest-param-test.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "client/mgmtd/ICommonMgmtdClient.h"
|
||||
#include "client/storage/StorageClient.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/GcManager.h"
|
||||
#include "meta/components/SessionManager.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestFileHelper : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestFileHelper, KVTypes);
|
||||
|
||||
TYPED_TEST(TestFileHelper, sync) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &fileHelper = cluster.meta().getFileHelper();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto result = co_await meta.create({SUPER_USER, fmt::format("file-{}", i), std::nullopt, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
|
||||
auto inode = Inode(result->stat);
|
||||
auto offset = folly::Random::rand64(100ULL << 30);
|
||||
auto length = folly::Random::rand64(16ULL << 20);
|
||||
co_await randomWrite(meta, storage, inode, offset, length);
|
||||
size_t total = offset + length;
|
||||
|
||||
FAULT_INJECTION_SET(10, 10);
|
||||
while (true) {
|
||||
auto stat = co_await meta.stat({SUPER_USER, inode.id, AtFlags()});
|
||||
CO_ASSERT_OK(stat);
|
||||
auto result = co_await fileHelper.queryLength({}, stat->stat, nullptr);
|
||||
if (!result.hasError()) {
|
||||
CO_ASSERT_EQ(*result, total);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
||||
}
|
||||
TYPED_TEST(TestFileHelper, remove) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &fileHelper = cluster.meta().getFileHelper();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
auto mgmtd = cluster.mgmtdClient();
|
||||
|
||||
struct File {
|
||||
Inode inode;
|
||||
size_t chunks = 0;
|
||||
size_t length = 0;
|
||||
};
|
||||
std::map<size_t, File> files;
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto result = co_await meta.create({SUPER_USER, fmt::format("file-{}", i), std::nullopt, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
File file{result->stat};
|
||||
file.inode.asFile().length = folly::Random::rand64(100ULL << 30);
|
||||
file.inode.asFile().truncateVer = folly::Random::rand64();
|
||||
std::set<size_t> chunks;
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto offset = folly::Random::rand64(100ULL << 30);
|
||||
auto length = folly::Random::rand64(16ULL << 20);
|
||||
co_await randomWrite(meta, storage, file.inode, offset, length);
|
||||
size_t firstChunk = offset / file.inode.asFile().layout.chunkSize.u64();
|
||||
size_t lastChunk = folly::divCeil(offset + length, file.inode.asFile().layout.chunkSize.u64());
|
||||
for (size_t chunk = firstChunk; chunk < lastChunk; chunk++) {
|
||||
if (chunks.contains(chunk)) {
|
||||
continue;
|
||||
}
|
||||
chunks.insert(chunk);
|
||||
file.chunks++;
|
||||
}
|
||||
file.length = std::max(file.length, offset + length);
|
||||
}
|
||||
files[i] = file;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
for (auto &[i, file] : files) {
|
||||
total += file.chunks;
|
||||
}
|
||||
auto query = co_await queryTotalChunks(storage, *mgmtd);
|
||||
CO_ASSERT_OK(query);
|
||||
CO_ASSERT_EQ(*query, total);
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
if (folly::Random::oneIn(2)) {
|
||||
continue;
|
||||
}
|
||||
while (true) {
|
||||
FAULT_INJECTION_SET(20, 10);
|
||||
files[i].inode.asFile().dynStripe = 0;
|
||||
auto result = co_await fileHelper.remove({}, files[i].inode, {}, folly::Random::rand32(8, 16));
|
||||
if (!result.hasError()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
total -= files[i].chunks;
|
||||
auto query = co_await queryTotalChunks(storage, *mgmtd);
|
||||
CO_ASSERT_OK(query);
|
||||
CO_ASSERT_EQ(*query, total);
|
||||
}
|
||||
}());
|
||||
}
|
||||
} // namespace hf3fs::meta::server
|
||||
326
tests/meta/components/TestGcManager.cc
Normal file
326
tests/meta/components/TestGcManager.cc
Normal file
@@ -0,0 +1,326 @@
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Invoke.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/futures/Future.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest-param-test.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "client/mgmtd/ICommonMgmtdClient.h"
|
||||
#include "client/storage/StorageClient.h"
|
||||
#include "client/storage/StorageClientInMem.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "fbs/core/user/User.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/GcManager.h"
|
||||
#include "meta/components/SessionManager.h"
|
||||
#include "meta/store/Inode.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestGcManager : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestGcManager, KVTypes);
|
||||
|
||||
TYPED_TEST(TestGcManager, entry) {
|
||||
fmt::print("{}\n", GcManager::formatGcEntry('d', UtcClock::now(), InodeId(folly::Random::rand64())));
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
auto a = UtcTime::fromMicroseconds(folly::Random::rand64(std::numeric_limits<int64_t>::max()));
|
||||
auto b = UtcTime::fromMicroseconds(folly::Random::rand64(std::numeric_limits<int64_t>::max()));
|
||||
if (a == b) continue;
|
||||
auto entryA = GcManager::formatGcEntry('f', a, InodeId::gcRoot());
|
||||
auto entryB = GcManager::formatGcEntry('f', b, InodeId::gcRoot());
|
||||
ASSERT_EQ(a < b, entryA < entryB) << fmt::format("a {}, b {}", entryA, entryB);
|
||||
auto parsed = GcManager::parseGcEntry(entryA);
|
||||
ASSERT_OK(parsed);
|
||||
ASSERT_EQ(parsed->first, a);
|
||||
ASSERT_EQ(parsed->second, InodeId::gcRoot());
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(TestGcManager, basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_remove_chunks_batch_size(1);
|
||||
config.mock_meta().gc().set_gc_directory_delay(2_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(4_s);
|
||||
config.mock_meta().gc().set_scan_batch(64);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
auto mgmtd = cluster.mgmtdClient();
|
||||
|
||||
std::vector<std::pair<InodeId, SessionInfo>> sessions;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
auto result = co_await meta.create({SUPER_USER, std::to_string(i) + ".writing", session, O_RDWR, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
co_await randomWrite(meta, storage, result->stat, folly::Random::rand64(100ULL << 30), 1ULL << 20);
|
||||
CO_ASSERT_OK(co_await meta.sync({SUPER_USER, result->stat.id, true, std::nullopt, std::nullopt}));
|
||||
sessions.push_back({result->stat.id, session});
|
||||
}
|
||||
|
||||
for (int i = 0; i < 512; i++) {
|
||||
auto result = co_await meta.create({SUPER_USER, std::to_string(i) + ".file", std::nullopt, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
co_await randomWrite(meta, storage, result->stat, folly::Random::rand64(100ULL << 30), 1ULL << 20);
|
||||
CO_ASSERT_OK(co_await meta.sync({SUPER_USER, result->stat.id, true, std::nullopt, std::nullopt}));
|
||||
}
|
||||
|
||||
GET_INODE_CNTS(numInodes);
|
||||
GET_DIRENTRY_CNTS(numDirentries);
|
||||
CO_ASSERT_SESSION_CNTS(10);
|
||||
|
||||
auto query = co_await queryTotalChunks(storage, *mgmtd);
|
||||
CO_ASSERT_OK(query);
|
||||
CO_ASSERT_NE(*query, 0);
|
||||
|
||||
auto begin = UtcClock::now();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, std::to_string(i) + ".writing", AtFlags(), false}));
|
||||
}
|
||||
for (int i = 0; i < 512; i++) {
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, std::to_string(i) + ".file", AtFlags(), false}));
|
||||
}
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
bool finish = false;
|
||||
while (!finish) {
|
||||
CO_ASSERT_LE(std::chrono::steady_clock::now() - start, std::chrono::seconds(60));
|
||||
co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
GET_INODE_CNTS(cnts);
|
||||
if (cnts == numInodes - 512) {
|
||||
finish = true;
|
||||
} else {
|
||||
if (cnts < numInodes) {
|
||||
CO_ASSERT_GE(UtcClock::now(), begin + 4_s);
|
||||
}
|
||||
fmt::print("{} inodes left\n", cnts);
|
||||
}
|
||||
}
|
||||
|
||||
CO_ASSERT_INODE_CNTS(numInodes - 512);
|
||||
CO_ASSERT_DIRENTRY_CNTS(numDirentries - 512);
|
||||
|
||||
for (auto [inode, session] : sessions) {
|
||||
CO_ASSERT_OK(co_await meta.close({SUPER_USER, inode, session, true, {}, {}}));
|
||||
}
|
||||
|
||||
start = std::chrono::steady_clock::now();
|
||||
finish = false;
|
||||
while (!finish) {
|
||||
CO_ASSERT_LE(std::chrono::steady_clock::now() - start, std::chrono::seconds(10));
|
||||
co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
GET_INODE_CNTS(cnts);
|
||||
if (cnts == numInodes - 512 - 10) {
|
||||
finish = true;
|
||||
} else {
|
||||
fmt::print("{} inodes left\n", cnts);
|
||||
}
|
||||
}
|
||||
|
||||
CO_ASSERT_INODE_CNTS(numInodes - 512 - 10);
|
||||
CO_ASSERT_DIRENTRY_CNTS(numDirentries - 512 - 10);
|
||||
CO_ASSERT_SESSION_CNTS(0);
|
||||
|
||||
auto queryAfterRemove = co_await queryTotalChunks(storage, *mgmtd);
|
||||
CO_ASSERT_OK(queryAfterRemove);
|
||||
CO_ASSERT_EQ(*queryAfterRemove, 0);
|
||||
|
||||
co_return;
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestGcManager, recursive) {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_remove_chunks_batch_size(1);
|
||||
config.mock_meta().gc().set_gc_directory_delay(3_s);
|
||||
config.mock_meta().gc().set_gc_file_delay(3_s);
|
||||
config.mock_meta().gc().set_scan_batch(128);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
folly::CPUThreadPoolExecutor exec(4);
|
||||
std::vector<folly::SemiFuture<Void>> tasks;
|
||||
|
||||
InodeId dir, dir1, dir2, dir3, dir4;
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
dir = (co_await meta.mkdirs({SUPER_USER, "dir", p644, false}))->stat.id;
|
||||
dir1 = (co_await meta.mkdirs({SUPER_USER, "dir/dir1", p644, false}))->stat.id;
|
||||
dir2 = (co_await meta.mkdirs({SUPER_USER, "dir/dir2", p644, false}))->stat.id;
|
||||
dir3 = (co_await meta.mkdirs({SUPER_USER, "dir/dir3", p644, false}))->stat.id;
|
||||
dir4 = (co_await meta.mkdirs({SUPER_USER, "dir/dir4", p644, false}))->stat.id;
|
||||
}());
|
||||
|
||||
auto worker = [&meta, dir, dir1, dir2, dir3, dir4](const size_t i) -> CoTask<void> {
|
||||
for (int j = 0; j < 64; j++) {
|
||||
auto fname = fmt::format("{}.{}", i, j);
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt(dir, fname), {}, O_RDONLY | O_EXCL, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt(dir1, fname), {}, O_RDONLY | O_EXCL, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt(dir2, fname), {}, O_RDONLY | O_EXCL, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt(dir3, fname), {}, O_RDONLY | O_EXCL, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, PathAt(dir4, fname), {}, O_RDONLY | O_EXCL, p644}));
|
||||
CO_ASSERT_OK(co_await meta.hardLink({SUPER_USER, PathAt(dir, fname), PathAt(dir, fname + ".1"), AtFlags()}));
|
||||
}
|
||||
co_return;
|
||||
};
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
auto task = folly::coro::co_invoke(worker, i).scheduleOn(&exec).start();
|
||||
tasks.push_back(std::move(task));
|
||||
}
|
||||
|
||||
for (auto &task : tasks) {
|
||||
task.wait();
|
||||
}
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
GET_INODE_CNTS(numInodes);
|
||||
GET_DIRENTRY_CNTS(numEntries);
|
||||
|
||||
auto expectedInodes = numInodes - 5 - 4 * 5 * 64;
|
||||
auto expectedEntries = numEntries - 5 - 4 * 6 * 64;
|
||||
|
||||
auto begin = UtcClock::now();
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "dir", AtFlags(), true}));
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
bool finish = false;
|
||||
while (!finish) {
|
||||
CO_ASSERT_LE(std::chrono::steady_clock::now() - start, std::chrono::seconds(120));
|
||||
co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
GET_INODE_CNTS(cnts);
|
||||
if (cnts <= expectedInodes) {
|
||||
finish = true;
|
||||
} else {
|
||||
if (cnts < numInodes - 1) {
|
||||
CO_ASSERT_GE(UtcClock::now(), begin + 6_s);
|
||||
}
|
||||
fmt::print("{} inodes left\n", cnts);
|
||||
}
|
||||
}
|
||||
|
||||
CO_ASSERT_INODE_CNTS(expectedInodes);
|
||||
CO_ASSERT_DIRENTRY_CNTS(expectedEntries);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestGcManager, delayThreshold) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
config.mock_meta().gc().set_gc_file_delay(5_min);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
// create file, write then delete
|
||||
auto session = MetaTestHelper::randomSession();
|
||||
auto result = co_await meta.create({SUPER_USER, "file", session, O_RDWR, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
co_await randomWrite(meta, storage, result->stat, folly::Random::rand64(100ULL << 30), 1ULL << 20);
|
||||
CO_ASSERT_OK(co_await meta.close({SUPER_USER, result->stat.id, session, true, std::nullopt, std::nullopt}));
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, "file", AtFlags(), false}));
|
||||
|
||||
co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
CO_ASSERT_OK(co_await meta.stat({SUPER_USER, result->stat.id, AtFlags()}));
|
||||
|
||||
// only enable gc delay if free space > 95
|
||||
config.mock_meta().gc().set_gc_delay_free_space_threshold(95);
|
||||
// wait 1s, then inode is deleted
|
||||
co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
CO_ASSERT_INODE_NOT_EXISTS(result->stat.id);
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestGcManager, recurisvePerm) {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().set_recursive_remove_perm_check(0);
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
config.mock_meta().gc().set_recursive_perm_check(true);
|
||||
|
||||
auto ua = flat::UserInfo(flat::Uid(1), flat::Gid(1));
|
||||
auto ub = flat::UserInfo(flat::Uid(2), flat::Gid(2));
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "trash", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "trash/gc-orphans", {}, 0, p644}));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({SUPER_USER, "share", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "share/ua", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ub, "share/ua/ub", p777, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ub, "share/ua/ub/noperm-empty", p700, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ub, "share/ua/ub/noperm-notempty/subdir", p700, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "share/ua/protected-directory", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(
|
||||
SetAttrReq::setIFlags(SUPER_USER, "share/ua/protected-directory", IFlags(FS_IMMUTABLE_FL))));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "share/ua/normal-dir", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.create({ua, "share/ua/normal-dir/protected1", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.create({ua, "share/ua/normal-dir/protected2", {}, 0, p644}));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(
|
||||
SetAttrReq::setIFlags(SUPER_USER, "share/ua/normal-dir/protected1", IFlags(FS_IMMUTABLE_FL))));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(
|
||||
SetAttrReq::setIFlags(SUPER_USER, "share/ua/normal-dir/protected2", IFlags(FS_IMMUTABLE_FL))));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "share/ua/data1/noperm/subdir", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.mkdirs({ua, "share/ua/data2/noperm/subdir", p755, true}));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(
|
||||
SetAttrReq::setPermission(SUPER_USER, "share/ua/data1/noperm", AtFlags(), {}, {}, flat::Permission(0))));
|
||||
CO_ASSERT_OK(co_await meta.setAttr(
|
||||
SetAttrReq::setPermission(SUPER_USER, "share/ua/data2/noperm", AtFlags(), {}, {}, flat::Permission(0))));
|
||||
|
||||
CO_ASSERT_OK(co_await meta.remove({ua, "share/ua", AtFlags(), true}));
|
||||
co_await folly::coro::sleep(5_s);
|
||||
CO_ASSERT_OK(co_await printTree(meta));
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestGcManager, DISABLED_BadFile) {
|
||||
// generate too many logs, disable by default
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().gc().set_enable(true);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
|
||||
// inject fault on chain 1
|
||||
co_await dynamic_cast<storage::client::StorageClientInMem &>(storage).injectErrorOnChain(
|
||||
flat::ChainId(100001),
|
||||
makeError(StorageClientCode::kChecksumMismatch));
|
||||
|
||||
// create file on chain 1, then remove
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
auto layout = Layout::newChainList(4096, {flat::ChainId(100001)});
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, fmt::format("{}.1", i), std::nullopt, O_RDONLY, p644, layout}));
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, fmt::format("{}.1", i), AtFlags(), false}));
|
||||
}
|
||||
// create file on chain 2. then remove
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
auto layout = Layout::newChainList(4096, {flat::ChainId(100002)});
|
||||
CO_ASSERT_OK(co_await meta.create({SUPER_USER, fmt::format("{}.2", i), std::nullopt, O_RDONLY, p644, layout}));
|
||||
CO_ASSERT_OK(co_await meta.remove({SUPER_USER, fmt::format("{}.2", i), AtFlags(), false}));
|
||||
}
|
||||
|
||||
co_await folly::coro::sleep(std::chrono::seconds(5));
|
||||
CO_ASSERT_INODE_CNTS(10010);
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
142
tests/meta/components/TestInodeIdAllocator.cc
Normal file
142
tests/meta/components/TestInodeIdAllocator.cc
Normal file
@@ -0,0 +1,142 @@
|
||||
#include <chrono>
|
||||
#include <folly/Synchronized.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/CurrentExecutor.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <gtest/gtest-typed-test.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/FaultInjection.h"
|
||||
#include "meta/components/InodeIdAllocator.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server::test {
|
||||
|
||||
template <typename KV>
|
||||
class TestInodeIdAllocator : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestInodeIdAllocator, KVTypes);
|
||||
|
||||
CoTask<void> doAllocate(std::shared_ptr<InodeIdAllocator> allocator,
|
||||
size_t times,
|
||||
folly::Synchronized<std::vector<InodeId>, std::mutex> &allocated,
|
||||
bool allowFailure = false) {
|
||||
for (size_t i = 1; i <= times; i++) {
|
||||
// since there is only one process, allocate shouldn't fail.
|
||||
auto result = co_await allocator->allocate(std::chrono::seconds(10));
|
||||
if (!allowFailure) {
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
if (result.hasError()) {
|
||||
CO_ASSERT_ERROR(result, MetaCode::kInodeIdAllocFailed);
|
||||
} else {
|
||||
allocated.lock()->push_back(result.value());
|
||||
co_await folly::coro::co_reschedule_on_current_executor;
|
||||
}
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInodeIdAllocator, Basic) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto kv = this->kvEngine();
|
||||
auto allocator = InodeIdAllocator::create(kv);
|
||||
|
||||
std::vector<InodeId> allocated;
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
auto result = co_await allocator->allocate();
|
||||
CO_ASSERT_OK(result);
|
||||
allocated.push_back(result.value());
|
||||
}
|
||||
|
||||
std::sort(allocated.begin(), allocated.end());
|
||||
for (size_t i = 1; i < allocated.size(); i++) {
|
||||
CO_ASSERT_GT(allocated.at(i), allocated.at(i - 1));
|
||||
}
|
||||
fmt::print("first {}, last {}\n", allocated.begin()->toHexString(), allocated.rbegin()->toHexString());
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInodeIdAllocator, MultiThreads) {
|
||||
folly::Synchronized<std::vector<InodeId>, std::mutex> allocated;
|
||||
std::vector<folly::SemiFuture<folly::Unit>> futures;
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
|
||||
auto kv = this->kvEngine();
|
||||
auto allocator = InodeIdAllocator::create(kv);
|
||||
|
||||
for (size_t i = 0; i < 32; i++) {
|
||||
// since there is only one process, allocate shouldn't fail.
|
||||
futures.push_back(doAllocate(allocator, 10000, allocated, false).scheduleOn(&exec).start());
|
||||
}
|
||||
for (auto &f : futures) {
|
||||
f.wait();
|
||||
}
|
||||
|
||||
auto lock = allocated.lock();
|
||||
std::sort(lock->begin(), lock->end());
|
||||
fmt::print("succ {}, min {}, max {}\n", lock->size(), lock->begin()->toHexString(), lock->rbegin()->toHexString());
|
||||
for (size_t i = 1; i < lock->size(); i++) {
|
||||
ASSERT_GT(lock->at(i), lock->at(i - 1));
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInodeIdAllocator, MultiAllocator) {
|
||||
folly::Synchronized<std::vector<InodeId>, std::mutex> allocated;
|
||||
std::vector<folly::SemiFuture<folly::Unit>> futures;
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
|
||||
auto kv = this->kvEngine();
|
||||
std::vector<std::shared_ptr<InodeIdAllocator>> vec;
|
||||
vec.reserve(32);
|
||||
for (size_t i = 0; i < 32; i++) {
|
||||
vec.push_back(InodeIdAllocator::create(kv));
|
||||
}
|
||||
for (size_t i = 0; i < 128; i++) {
|
||||
auto &allocator = vec[i % vec.size()];
|
||||
futures.push_back(doAllocate(allocator, 10000, allocated, true).scheduleOn(&exec).start());
|
||||
}
|
||||
for (auto &f : futures) {
|
||||
f.wait();
|
||||
}
|
||||
|
||||
auto lock = allocated.lock();
|
||||
std::sort(lock->begin(), lock->end());
|
||||
fmt::print("succ {}, min {}, max {}\n", lock->size(), lock->begin()->toHexString(), lock->rbegin()->toHexString());
|
||||
for (size_t i = 1; i < lock->size(); i++) {
|
||||
ASSERT_GT(lock->at(i), lock->at(i - 1));
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(TestInodeIdAllocator, FaultInjection) {
|
||||
folly::Synchronized<std::vector<InodeId>, std::mutex> allocated;
|
||||
std::vector<folly::SemiFuture<folly::Unit>> futures;
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
|
||||
auto kv = this->kvEngine();
|
||||
auto allocator = InodeIdAllocator::create(kv);
|
||||
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
FAULT_INJECTION_SET(10, -1);
|
||||
futures.push_back(doAllocate(allocator, 10000, allocated, true).scheduleOn(&exec).start());
|
||||
}
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
futures.push_back(doAllocate(allocator, 10000, allocated, false).scheduleOn(&exec).start());
|
||||
}
|
||||
for (auto &f : futures) {
|
||||
f.wait();
|
||||
}
|
||||
|
||||
auto lock = allocated.lock();
|
||||
std::sort(lock->begin(), lock->end());
|
||||
fmt::print("succ {}, min {}, max {}\n", lock->size(), lock->begin()->toHexString(), lock->rbegin()->toHexString());
|
||||
for (size_t i = 1; i < lock->size(); i++) {
|
||||
ASSERT_GT(lock->at(i), lock->at(i - 1));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server::test
|
||||
297
tests/meta/components/TestSessionManager.cc
Normal file
297
tests/meta/components/TestSessionManager.cc
Normal file
@@ -0,0 +1,297 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest-param-test.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "client/mgmtd/ICommonMgmtdClient.h"
|
||||
#include "client/storage/StorageClient.h"
|
||||
#include "common/app/ClientId.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "common/utils/Uuid.h"
|
||||
#include "fbs/meta/Common.h"
|
||||
#include "fbs/meta/Schema.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "meta/components/GcManager.h"
|
||||
#include "meta/components/SessionManager.h"
|
||||
#include "meta/store/FileSession.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/meta/MetaTestBase.h"
|
||||
|
||||
namespace hf3fs::meta::server {
|
||||
|
||||
template <typename KV>
|
||||
class TestSessionManager : public MetaTestBase<KV> {};
|
||||
|
||||
using KVTypes = ::testing::Types<mem::MemKV, fdb::DB>;
|
||||
TYPED_TEST_SUITE(TestSessionManager, KVTypes);
|
||||
|
||||
TYPED_TEST(TestSessionManager, basic) {
|
||||
// for (size_t i = 0; i < 10; i++) {
|
||||
// auto client = Uuid::random();
|
||||
// auto session = Uuid::random();
|
||||
// auto key = FileSession::packKey(client, session);
|
||||
// auto unpacked = FileSession::unpackByClientKey(key);
|
||||
// ASSERT_EQ(unpacked->first, client);
|
||||
// ASSERT_EQ(unpacked->second, session);
|
||||
// }
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto inode = InodeId(folly::Random::rand64());
|
||||
auto session = Uuid::random();
|
||||
auto key = FileSession::packKey(inode, session);
|
||||
auto unpacked = FileSession::unpackByInodeKey(key);
|
||||
ASSERT_EQ(unpacked->first, inode);
|
||||
ASSERT_EQ(unpacked->second, session);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSessionManager, byInode) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto mgmtd = std::dynamic_pointer_cast<tests::FakeMgmtdClient>(cluster.mgmtdClient());
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto inode = InodeId(folly::Random::rand64());
|
||||
size_t numSessions = folly::Random::rand32(5, 32);
|
||||
for (size_t j = 0; j < numSessions; j++) {
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto session = FileSession::create(inode, ClientId::random(), Uuid::random());
|
||||
CO_ASSERT_OK(co_await session.store(*txn));
|
||||
});
|
||||
}
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto sessions = co_await FileSession::list(*txn, inode, false);
|
||||
CO_ASSERT_OK(sessions);
|
||||
CO_ASSERT_EQ(sessions->size(), numSessions);
|
||||
auto hasSession = co_await FileSession::snapshotCheckExists(*txn, inode);
|
||||
CO_ASSERT_OK(hasSession);
|
||||
CO_ASSERT_TRUE(*hasSession);
|
||||
});
|
||||
|
||||
READ_WRITE_TRANSACTION_OK({ CO_ASSERT_OK(co_await FileSession::removeAll(*txn, inode)); });
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto sessions = co_await FileSession::list(*txn, inode, false);
|
||||
CO_ASSERT_OK(sessions);
|
||||
CO_ASSERT_EQ(sessions->size(), 0);
|
||||
auto hasSession = co_await FileSession::snapshotCheckExists(*txn, inode);
|
||||
CO_ASSERT_OK(hasSession);
|
||||
CO_ASSERT_FALSE(*hasSession);
|
||||
});
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSessionManager, scan) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto cluster = this->createMockCluster();
|
||||
auto mgmtd = std::dynamic_pointer_cast<tests::FakeMgmtdClient>(cluster.mgmtdClient());
|
||||
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
for (size_t i = 0; i < 500; i++) {
|
||||
auto inode = InodeId(folly::Random::rand64(1 << 10, std::numeric_limits<uint64_t>::max()));
|
||||
auto session = FileSession::create(inode, ClientId::random(), Uuid::random());
|
||||
CO_ASSERT_OK(co_await session.store(*txn));
|
||||
auto prune = FileSession::createPrune(session.clientId, session.sessionId);
|
||||
CO_ASSERT_OK(co_await prune.store(*txn));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::map<Uuid, FileSession> sessions;
|
||||
for (size_t i = 0; i < FileSession::kShard; i++) {
|
||||
std::vector<FileSession> vec;
|
||||
bool more = true;
|
||||
while (more) {
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto sessions = co_await FileSession::scan(*txn, i, !vec.empty() ? std::optional(vec.back()) : std::nullopt);
|
||||
CO_ASSERT_OK(sessions);
|
||||
more = !sessions->empty();
|
||||
vec.insert(vec.end(), sessions->begin(), sessions->end());
|
||||
});
|
||||
}
|
||||
for (auto &s : vec) {
|
||||
sessions.emplace(s.sessionId, s);
|
||||
}
|
||||
}
|
||||
CO_ASSERT_EQ(sessions.size(), 100 * 500);
|
||||
|
||||
READ_ONLY_TRANSACTION({
|
||||
auto prune = co_await FileSession::listPrune(*txn, sessions.size() * 2);
|
||||
CO_ASSERT_OK(prune);
|
||||
CO_ASSERT_EQ(prune->size(), sessions.size());
|
||||
for (auto s : *prune) {
|
||||
CO_ASSERT_TRUE(sessions.find(s.sessionId) != sessions.end());
|
||||
sessions.erase(s.sessionId);
|
||||
}
|
||||
});
|
||||
CO_ASSERT_TRUE(sessions.empty());
|
||||
}());
|
||||
}
|
||||
|
||||
// TYPED_TEST(TestSessionManager, byClient) {
|
||||
// folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
// auto cluster = this->createMockCluster();
|
||||
// auto &sessionManager = cluster.meta().getSessionManager();
|
||||
// auto mgmtd = std::dynamic_pointer_cast<tests::FakeMgmtdClient>(cluster.mgmtdClient());
|
||||
// for (auto clientId : mgmtd->getActiveClients()) {
|
||||
// size_t numSessions = folly::Random::rand32(5, 32);
|
||||
// for (size_t j = 0; j < numSessions; j++) {
|
||||
// READ_WRITE_TRANSACTION_OK({
|
||||
// auto session = FileSession::create(InodeId(folly::Random::rand32()), ClientId(clientId), Uuid::random());
|
||||
// CO_ASSERT_OK(co_await session.store(*txn));
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// READ_ONLY_TRANSACTION({
|
||||
// auto sessions = co_await FileSession::list(*txn, clientId, true);
|
||||
// CO_ASSERT_OK(sessions);
|
||||
// CO_ASSERT_EQ(sessions->size(), numSessions);
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// std::vector<std::pair<Uuid, size_t>> deadClients;
|
||||
// for (size_t i = 0; i < 10; i++) {
|
||||
// auto clientId = Uuid::random();
|
||||
// size_t numSessions = folly::Random::rand32(1, 32);
|
||||
// deadClients.emplace_back(clientId, numSessions);
|
||||
// for (size_t j = 0; j < numSessions; j++) {
|
||||
// READ_WRITE_TRANSACTION_OK({
|
||||
// auto session = FileSession::create(InodeId(folly::Random::rand32()), ClientId(clientId), Uuid::random());
|
||||
// CO_ASSERT_OK(co_await session.store(*txn));
|
||||
// });
|
||||
// }
|
||||
// READ_ONLY_TRANSACTION({
|
||||
// auto sessions = co_await FileSession::list(*txn, clientId, false);
|
||||
// CO_ASSERT_OK(sessions);
|
||||
// CO_ASSERT_EQ(sessions->size(), numSessions);
|
||||
// });
|
||||
// }
|
||||
// READ_ONLY_TRANSACTION({
|
||||
// auto all = co_await sessionManager.listClients();
|
||||
// CO_ASSERT_OK(all);
|
||||
// CO_ASSERT_EQ(all->size(), deadClients.size() + mgmtd->getActiveClients().size());
|
||||
// for (auto client : *all) {
|
||||
// auto active = mgmtd->getActiveClients().contains(client.clientId.uuid);
|
||||
// CO_ASSERT_EQ(active, client.active);
|
||||
// CO_ASSERT_NE(client.sessions, 0);
|
||||
// }
|
||||
// });
|
||||
// }());
|
||||
// }
|
||||
|
||||
TYPED_TEST(TestSessionManager, prune) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().session_manager().set_enable(true);
|
||||
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto mgmtd = std::dynamic_pointer_cast<tests::FakeMgmtdClient>(cluster.mgmtdClient());
|
||||
|
||||
auto now = UtcClock::now().toMicroseconds();
|
||||
auto minutes = 60 * 1000000;
|
||||
|
||||
std::vector<Uuid> deadClients;
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
deadClients.push_back(Uuid::random());
|
||||
}
|
||||
std::vector<FileSession> active, steal, future;
|
||||
for (size_t i = 0; i < folly::Random::rand32(1000); i++) {
|
||||
auto client = mgmtd->getOneActiveClient();
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto session = FileSession::create(InodeId(folly::Random::rand32()),
|
||||
client,
|
||||
Uuid::random(),
|
||||
UtcTime::fromMicroseconds(now - minutes));
|
||||
active.push_back(session);
|
||||
CO_ASSERT_OK(co_await session.store(*txn));
|
||||
|
||||
auto pruneSessionId = Uuid::random();
|
||||
CO_ASSERT_OK(
|
||||
co_await FileSession::create(InodeId(folly::Random::rand32()), client, pruneSessionId).store(*txn));
|
||||
CO_ASSERT_OK(co_await FileSession::createPrune(client, pruneSessionId).store(*txn));
|
||||
CO_ASSERT_OK(co_await FileSession::createPrune(ClientId(Uuid::random()), pruneSessionId).store(*txn));
|
||||
});
|
||||
}
|
||||
for (size_t i = 0; i < folly::Random::rand32(1000); i++) {
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto session = FileSession::create(InodeId(folly::Random::rand32()),
|
||||
ClientId(deadClients.at(folly::Random::rand32(deadClients.size()))),
|
||||
Uuid::random(),
|
||||
UtcTime::fromMicroseconds(now - minutes));
|
||||
steal.push_back(session);
|
||||
CO_ASSERT_OK(co_await session.store(*txn));
|
||||
});
|
||||
}
|
||||
for (size_t i = 0; i < folly::Random::rand32(1000); i++) {
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto session = FileSession::create(InodeId(folly::Random::rand32()),
|
||||
ClientId(deadClients.at(folly::Random::rand32(deadClients.size()))),
|
||||
Uuid::random(),
|
||||
UtcTime::fromMicroseconds(now + minutes));
|
||||
future.push_back(session);
|
||||
CO_ASSERT_OK(co_await session.store(*txn));
|
||||
});
|
||||
}
|
||||
|
||||
co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
fmt::print("{} {}\n", active.size(), future.size());
|
||||
CO_ASSERT_SESSION_CNTS(active.size() + future.size());
|
||||
|
||||
mgmtd->clearActiveClient();
|
||||
co_await folly::coro::sleep(std::chrono::seconds(1));
|
||||
CO_ASSERT_SESSION_CNTS(future.size());
|
||||
}());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSessionManager, syncOnPrune) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
MockCluster::Config config;
|
||||
config.mock_meta().set_sync_on_prune_session(true);
|
||||
config.mock_meta().session_manager().set_enable(true);
|
||||
config.mock_meta().session_manager().set_sync_on_prune_session(true);
|
||||
auto cluster = this->createMockCluster(config);
|
||||
auto &meta = cluster.meta().getOperator();
|
||||
auto &storage = cluster.meta().getStorageClient();
|
||||
auto mgmtd = std::dynamic_pointer_cast<tests::FakeMgmtdClient>(cluster.mgmtdClient());
|
||||
|
||||
auto result = co_await meta.create({SUPER_USER, PathAt("file"), std::nullopt, O_RDONLY, p644});
|
||||
CO_ASSERT_OK(result);
|
||||
auto &inode = result->stat;
|
||||
auto length = folly::Random::rand64(2 << 20, 4 << 20);
|
||||
co_await randomWrite(meta, storage, inode, 0, length);
|
||||
|
||||
// create a dead session
|
||||
READ_WRITE_TRANSACTION_OK({
|
||||
auto session = FileSession::create(
|
||||
inode.id,
|
||||
ClientId(Uuid::random()),
|
||||
Uuid::random(),
|
||||
UtcTime::fromMicroseconds(UtcClock::now().toMicroseconds() - 60 * 60 * 1000000L /* 1h */));
|
||||
CO_ASSERT_OK(co_await session.store(*txn));
|
||||
});
|
||||
|
||||
// wait sometime, inode should be synced
|
||||
CO_ASSERT_SESSION_CNTS(1);
|
||||
co_await folly::coro::sleep(std::chrono::seconds(3));
|
||||
CO_ASSERT_SESSION_CNTS(0);
|
||||
|
||||
auto stat = co_await meta.stat({SUPER_USER, PathAt("file"), AtFlags()});
|
||||
CO_ASSERT_OK(stat);
|
||||
CO_ASSERT_EQ(stat->stat.asFile().length, length);
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::meta::server
|
||||
40
tests/meta/components/TestUserToken.cc
Normal file
40
tests/meta/components/TestUserToken.cc
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <folly/Random.h>
|
||||
|
||||
#include "core/user/UserToken.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::meta::server::test {
|
||||
namespace {
|
||||
class UserTokenTest : public ::testing::Test {
|
||||
protected:
|
||||
void test(uint32_t uid, uint64_t randomv) {
|
||||
auto token = core::encodeUserToken(uid, randomv);
|
||||
XLOGF(DBG, "encode({}, {}) -> {}", uid, randomv, token);
|
||||
|
||||
auto decodeRes = core::decodeUserToken(token);
|
||||
ASSERT_OK(decodeRes);
|
||||
|
||||
auto [duid, dts] = *decodeRes;
|
||||
ASSERT_EQ(uid, duid);
|
||||
ASSERT_EQ(randomv, dts);
|
||||
|
||||
auto decodeRes2 = core::decodeUidFromUserToken(token);
|
||||
ASSERT_OK(decodeRes2);
|
||||
ASSERT_EQ(uid, *decodeRes2);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(UserTokenTest, test) {
|
||||
for (uint32_t i = 0; i < 100; ++i) {
|
||||
for (uint64_t j = 0; j < 100; ++j) {
|
||||
test(i, j);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < 10000; ++i) {
|
||||
auto uid = folly::Random::rand32();
|
||||
auto randomv = folly::Random::rand64();
|
||||
test(uid, randomv);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
} // namespace hf3fs::meta::server::test
|
||||
Reference in New Issue
Block a user