Initial commit

This commit is contained in:
dev
2025-02-27 21:53:53 +08:00
commit 815e55e4c0
1291 changed files with 185445 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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