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,6 @@
add_subdirectory(cli)
add_subdirectory(bin)
add_subdirectory(mgmtd)
add_subdirectory(meta)
add_subdirectory(storage)
add_subdirectory(core)

View File

@@ -0,0 +1,2 @@
target_add_bin(admin_cli "admin_cli.cc" admin-cli)

238
src/client/bin/admin_cli.cc Normal file
View File

@@ -0,0 +1,238 @@
#include <boost/algorithm/string.hpp>
#include <exception>
#include <folly/experimental/coro/BlockingWait.h>
#include <folly/init/Init.h>
#include <folly/logging/xlog.h>
#include <iostream>
#include "client/cli/admin/AdminEnv.h"
#include "client/cli/admin/registerAdminCommands.h"
#include "client/cli/common/Utils.h"
#include "client/meta/MetaClient.h"
#include "client/mgmtd/MgmtdClientForAdmin.h"
#include "common/logging/LogInit.h"
#include "common/net/Client.h"
#include "common/net/ib/IBDevice.h"
#include "common/utils/ConfigBase.h"
#include "fdb/FDBContext.h"
#include "fdb/FDBKVEngine.h"
#include "stubs/MetaService/MetaServiceStub.h"
#include "stubs/common/RealStubFactory.h"
#include "stubs/core/CoreServiceStub.h"
#include "stubs/mgmtd/MgmtdServiceStub.h"
DECLARE_bool(release_version);
using namespace hf3fs;
using namespace hf3fs::client;
using namespace hf3fs::client::cli;
using namespace hf3fs::stubs;
using hf3fs::client::CoreClient;
using hf3fs::meta::client::MetaClient;
using hf3fs::storage::client::StorageClient;
namespace {
class UserConfig : public ConfigBase<UserConfig> {
CONFIG_ITEM(uid, int64_t{-1});
CONFIG_ITEM(gid, int64_t{-1});
CONFIG_ITEM(gids, std::vector<uint32_t>{});
CONFIG_ITEM(token, "");
};
class Config : public ConfigBase<Config> {
CONFIG_OBJ(client, net::Client::Config);
CONFIG_OBJ(ib_devices, net::IBDevice::Config);
CONFIG_ITEM(cluster_id, "");
CONFIG_OBJ(user_info, UserConfig);
CONFIG_ITEM(log, "DBG:normal; normal=file:path=cli.log,async=true,sync_level=ERR", ConfigCheckers::checkNotEmpty);
CONFIG_ITEM(num_timeout_ms, 1000L);
CONFIG_ITEM(verbose, false);
CONFIG_ITEM(profile, false);
CONFIG_ITEM(break_multi_line_command_on_failure, false);
CONFIG_OBJ(fdb, kv::fdb::FDBConfig);
CONFIG_OBJ(mgmtd_client, MgmtdClientForAdmin::Config, [&](auto &c) {
c.set_enable_auto_refresh(true);
c.set_auto_refresh_interval(1_s);
c.set_enable_auto_heartbeat(false);
c.set_enable_auto_extend_client_session(false);
});
CONFIG_OBJ(meta_client, MetaClient::Config);
CONFIG_OBJ(storage_client, StorageClient::Config);
CONFIG_OBJ(monitor, hf3fs::monitor::Monitor::Config);
};
flat::UserInfo generateUserInfo(const UserConfig &cfg) {
auto uid = cfg.uid() != -1 ? cfg.uid() : static_cast<int64_t>(geteuid());
auto gid = cfg.gid() != -1 ? cfg.gid() : static_cast<int64_t>(getegid());
std::vector<flat::Gid> groups;
if (cfg.gids().empty()) {
auto n = getgroups(0, nullptr);
std::vector<gid_t> gs(n);
if (getgroups(n, gs.data()) < 0) {
XLOGF(ERR, "failed to get supplementary groups for uid {}, going with egid {} only", uid, gid);
}
for (auto g : gs) groups.emplace_back(g);
} else {
for (auto g : cfg.gids()) groups.emplace_back(g);
}
return flat::UserInfo(flat::Uid(uid), flat::Gid(gid), std::move(groups), cfg.token());
}
} // namespace
int main(int argc, char **argv) {
Config config;
auto initResult = config.init(&argc, &argv);
XLOGF_IF(FATAL, !initResult, "Load config from flags failed with {}", initResult.error().describe());
if (FLAGS_release_version) {
fmt::print("{}\n{}\n", VersionInfo::full(), VersionInfo::commitHashFull());
return 0;
}
logging::initOrDie(config.log());
hf3fs::SysResource::increaseProcessFDLimit(524288);
std::shared_ptr<net::Client> client;
std::shared_ptr<kv::fdb::FDBContext> fdbContext;
std::shared_ptr<kv::IKVEngine> kvEngine;
std::shared_ptr<MgmtdClientForAdmin> mgmtdClient;
std::shared_ptr<MetaClient> metaClient;
std::shared_ptr<StorageClient> storageClient;
std::shared_ptr<CoreClient> coreClient;
auto clientId = ClientId::random();
auto ensureIbInited = [&] {
[[maybe_unused]] static bool inited = [&] {
auto ibResult = hf3fs::net::IBManager::start(config.ib_devices());
XLOGF_IF(FATAL, !ibResult, "Failed to start IBManager: {}.", ibResult.error().describe());
return true;
}();
};
SCOPE_EXIT { hf3fs::net::IBManager::stop(); };
auto monitorResult = hf3fs::monitor::Monitor::start(config.monitor());
XLOGF_IF(CRITICAL, !monitorResult, "Start monitor failed: {}", monitorResult.error().describe());
SCOPE_EXIT { hf3fs::monitor::Monitor::stop(); };
auto ensureClient = [&] {
[[maybe_unused]] static bool inited = [&] {
ensureIbInited();
client = std::make_shared<net::Client>(config.client());
client->start();
return true;
}();
};
auto ensureMgmtdClient = [&] {
[[maybe_unused]] static bool inited = [&] {
ensureClient();
mgmtdClient =
std::make_shared<MgmtdClientForAdmin>(config.cluster_id(),
std::make_unique<RealStubFactory<mgmtd::MgmtdServiceStub>>(
[&client](net::Address addr) { return client->serdeCtx(addr); }),
config.mgmtd_client());
folly::coro::blockingWait(mgmtdClient->start(&client->tpg().bgThreadPool().randomPick()));
folly::coro::blockingWait(mgmtdClient->refreshRoutingInfo(/*force=*/false));
return true;
}();
};
AdminEnv env;
env.userInfo = generateUserInfo(config.user_info());
env.kvEngineGetter = [&] {
[[maybe_unused]] static bool inited = [&] {
fdbContext = kv::fdb::FDBContext::create(config.fdb());
kvEngine = std::make_unique<kv::FDBKVEngine>(fdbContext->getDB());
return true;
}();
return kvEngine;
};
env.clientGetter = [&] {
ensureClient();
return client;
};
env.mgmtdClientGetter = [&] {
ensureMgmtdClient();
if (auto ri = mgmtdClient->getRoutingInfo(); !ri || !ri->raw())
throw StatusException(Status(MgmtdClientCode::kRoutingInfoNotReady));
return mgmtdClient;
};
env.unsafeMgmtdClientGetter = [&] {
ensureMgmtdClient();
return mgmtdClient;
};
env.storageClientGetter = [&] {
[[maybe_unused]] static bool inited = [&] {
ensureMgmtdClient();
storageClient = StorageClient::create(clientId, config.storage_client(), *mgmtdClient);
return true;
}();
if (auto ri = mgmtdClient->getRoutingInfo(); !ri || !ri->raw())
throw StatusException(Status(MgmtdClientCode::kRoutingInfoNotReady));
return storageClient;
};
env.metaClientGetter = [&] {
[[maybe_unused]] static bool inited = [&] {
ensureMgmtdClient();
metaClient = std::make_shared<MetaClient>(
clientId,
config.meta_client(),
std::make_unique<MetaClient::StubFactory>([&client](net::Address addr) { return client->serdeCtx(addr); }),
mgmtdClient,
env.storageClientGetter(),
false);
return true;
}();
if (auto ri = mgmtdClient->getRoutingInfo(); !ri || !ri->raw())
throw StatusException(Status(MgmtdClientCode::kRoutingInfoNotReady));
return metaClient;
};
env.coreClientGetter = [&] {
[[maybe_unused]] static bool inited = [&] {
ensureClient();
coreClient = std::make_shared<CoreClient>(std::make_unique<RealStubFactory<core::CoreServiceStub>>(
[&client](net::Address addr) { return client->serdeCtx(addr); }));
return true;
}();
return coreClient;
};
std::string cmd = argc > 1 ? fmt::format("{}", fmt::join(&argv[1], &argv[argc], " ")) : "";
XLOGF(INFO, "cli start cmd = \"{}\" verbose = {} profile = {}", cmd, config.verbose(), config.profile());
Dispatcher dispatcher;
auto result = folly::coro::blockingWait([&]() -> CoTryTask<void> {
auto res = co_await registerAdminCommands(dispatcher);
if (res.hasError()) {
fmt::print("Register commands failed: {}", res.error());
CO_RETURN_ERROR(res);
}
co_return co_await dispatcher.run(
env,
[&env] { return env.currentDir; },
cmd,
config.verbose(),
config.profile(),
config.break_multi_line_command_on_failure());
}());
if (mgmtdClient) {
folly::coro::blockingWait(mgmtdClient->stop());
}
if (client) {
client->stopAndJoin();
}
return result.hasError();
}

View File

@@ -0,0 +1,2 @@
add_subdirectory(common)
add_subdirectory(admin)

View File

@@ -0,0 +1,25 @@
#pragma once
#include "client/cli/common/IEnv.h"
#include "client/core/CoreClient.h"
#include "client/meta/MetaClient.h"
#include "client/mgmtd/IMgmtdClientForAdmin.h"
#include "client/storage/StorageClient.h"
#include "common/kv/IKVEngine.h"
#include "fbs/core/user/User.h"
namespace hf3fs::client::cli {
struct AdminEnv : IEnv {
flat::UserInfo userInfo{flat::Uid{0}, flat::Gid{0}, ""};
meta::InodeId currentDirId = meta::InodeId::root();
String currentDir = "/";
std::function<std::shared_ptr<net::Client>()> clientGetter;
std::function<std::shared_ptr<meta::client::MetaClient>()> metaClientGetter;
std::function<std::shared_ptr<kv::IKVEngine>()> kvEngineGetter;
std::function<std::shared_ptr<IMgmtdClientForAdmin>()> mgmtdClientGetter;
std::function<std::shared_ptr<IMgmtdClientForAdmin>()> unsafeMgmtdClientGetter;
std::function<std::shared_ptr<storage::client::StorageClient>()> storageClientGetter;
std::function<std::shared_ptr<CoreClient>()> coreClientGetter;
};
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,402 @@
#include "AdminUserCtrl.h"
#include <fmt/chrono.h>
#include <string>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/kv/WithTransaction.h"
#include "common/utils/ArgParse.h"
#include "common/utils/OptionalUtils.h"
#include "common/utils/Result.h"
#include "common/utils/StringUtils.h"
#include "common/utils/Transform.h"
#include "common/utils/Uuid.h"
#include "core/user/UserStore.h"
#include "core/user/UserToken.h"
#include "fdb/FDBRetryStrategy.h"
namespace hf3fs::client::cli {
namespace {
using flat::Gid;
using flat::Uid;
kv::FDBRetryStrategy getRetryStrategy() { return kv::FDBRetryStrategy({1_s, 10, false}); }
CoTryTask<void> ensureAdmin([[maybe_unused]] kv::IReadOnlyTransaction &txn,
[[maybe_unused]] core::UserStore &store,
[[maybe_unused]] std::string_view token,
[[maybe_unused]] std::optional<flat::Uid> self = std::nullopt) {
auto uid = core::decodeUidFromUserToken(token);
if (UNLIKELY(uid.hasError())) {
co_return makeError(StatusCode::kAuthenticationFail, "Token decode failed");
}
auto user = co_await store.getUser(txn, *uid);
CO_RETURN_ON_ERROR(user);
if (!user->has_value()) {
co_return makeError(StatusCode::kAuthenticationFail, fmt::format("Uid {} not found", uid->toUnderType()));
}
if (auto res = user->value().validateToken(token, UtcClock::now()); res.hasError()) {
XLOGF(ERR, "Token validate failed: {}, uid {}", res.error(), *uid);
co_return makeError(StatusCode::kAuthenticationFail, "Token validate failed");
}
if (*uid != 0 && !user->value().admin && (!self || *self != *uid)) {
co_return makeError(StatusCode::kAuthenticationFail, "Not Admin");
}
co_return Void{};
}
void printUserAttr(Dispatcher::OutputTable &table, flat::Uid uid, const flat::UserAttr &attr) {
table.push_back({"Uid", std::to_string(uid)});
table.push_back({"Name", attr.name});
table.push_back({"Token", fmt::format("{}(Expired at N/A)", attr.token)});
for (auto it = attr.tokens.rbegin(); it != attr.tokens.rend(); ++it) {
const auto &sa = *it;
table.push_back({"Token", fmt::format("{}(Expired at {})", sa.token, sa.endTime.YmdHMS())});
}
table.push_back({"IsRootUser", attr.root ? "true" : "false"});
table.push_back({"IsAdmin", attr.admin ? "true" : "false"});
table.push_back({"Gid", std::to_string(attr.gid)});
auto groups = fmt::format("{}", fmt::join(attr.groups, ", "));
table.push_back({"SupplementaryGids", groups});
}
struct UserAddHandler {
static auto getParser() {
argparse::ArgumentParser cmd("user-add");
cmd.add_argument("uid").scan<'u', uint32_t>();
cmd.add_argument("name");
cmd.add_argument("--root").help("is root or not").default_value(false).implicit_value(true);
cmd.add_argument("--admin").help("is admin or not").default_value(false).implicit_value(true);
cmd.add_argument("--groups").help("group ids separated by comma (',')");
cmd.add_argument("--token");
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
auto uid = Uid(parser.get<uint32_t>("uid"));
auto name = parser.get<std::string>("name");
auto isRootUser = parser.get<bool>("--root");
auto isAdmin = parser.get<bool>("--admin");
auto token = parser.present<String>("--token").value_or("");
auto gids = splitAndTransform(parser.present<String>("--groups").value_or(""), boost::is_any_of(","), [](auto s) {
return flat::Gid(folly::to<uint32_t>(s));
});
core::UserStore store;
auto handler = [&](kv::IReadWriteTransaction &txn) -> CoTryTask<flat::UserAttr> {
auto userList = co_await store.listUsers(txn);
CO_RETURN_ON_ERROR(userList);
if (userList->empty()) {
if (!isAdmin) {
co_return makeError(StatusCode::kInvalidArg, "The first user must be admin");
}
} else {
CO_RETURN_ON_ERROR(co_await ensureAdmin(txn, store, env.userInfo.token));
}
co_return co_await store.addUser(txn, uid, name, gids, isRootUser, isAdmin, token);
};
auto commitRes = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadWriteTransaction(), std::move(handler));
CO_RETURN_ON_ERROR(commitRes);
printUserAttr(output, uid, *commitRes);
co_return output;
}
};
struct UserRemoveHandler {
static auto getParser() {
argparse::ArgumentParser cmd("user-remove");
cmd.add_argument("uid").scan<'u', uint32_t>();
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
auto uid = Uid(parser.get<uint32_t>("uid"));
core::UserStore store;
auto handler = [&](kv::IReadWriteTransaction &txn) -> CoTryTask<void> {
CO_RETURN_ON_ERROR(co_await ensureAdmin(txn, store, env.userInfo.token));
co_return co_await store.removeUser(txn, uid);
};
auto commitRes = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadWriteTransaction(), std::move(handler));
CO_RETURN_ON_ERROR(commitRes);
co_return output;
}
};
struct UserSetHandler {
static auto getParser() {
argparse::ArgumentParser cmd("user-set");
cmd.add_argument("uid").scan<'u', uint32_t>();
cmd.add_argument("--name").help("name of the new user");
cmd.add_argument("--root").scan<'i', int32_t>().help("1: set; 0: unset");
cmd.add_argument("--admin").scan<'i', int32_t>().help("1: set; 0: unset");
cmd.add_argument("--groups").help("group ids separated by comma (',')");
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
auto uid = Uid(parser.get<uint32_t>("uid"));
auto name = parser.present("--name");
auto isRootValue = parser.present<int32_t>("--root");
auto isAdminValue = parser.present<int32_t>("--admin");
auto groups = splitAndTransform(parser.present<String>("--groups").value_or(""), boost::is_any_of(","), [](auto s) {
return flat::Gid(folly::to<uint32_t>(s));
});
if (isRootValue) {
ENSURE_USAGE(*isRootValue == 0 || *isRootValue == 1, "-r should be 0 or 1");
}
if (isAdminValue) {
ENSURE_USAGE(*isAdminValue == 0 || *isAdminValue == 1, "--admin should be 0 or 1");
}
auto toBool = [](auto v) -> bool { return v; };
std::optional<bool> isRoot = optionalMap(isRootValue, toBool);
std::optional<bool> isAdmin = optionalMap(isAdminValue, toBool);
core::UserStore store;
auto handler = [&](kv::IReadWriteTransaction &txn) -> CoTryTask<meta::UserAttr> {
CO_RETURN_ON_ERROR(co_await ensureAdmin(txn, store, env.userInfo.token));
co_return co_await store.setUserAttr(txn,
uid,
name ? std::string_view(*name) : std::string_view{},
groups.empty() ? nullptr : &groups,
isRoot,
isAdmin);
};
auto commitRes = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadWriteTransaction(), std::move(handler));
CO_RETURN_ON_ERROR(commitRes);
printUserAttr(output, uid, *commitRes);
co_return output;
}
};
struct UserSetTokenHandler {
static auto getParser() {
argparse::ArgumentParser cmd("user-set-token");
cmd.add_argument("uid").scan<'u', uint32_t>();
cmd.add_argument("--new").help("generate new token").default_value(false).implicit_value(true);
cmd.add_argument("--token");
cmd.add_argument("--invalidate-existed-tokens").default_value(false).implicit_value(true);
cmd.add_argument("--max-active-tokens").scan<'u', uint32_t>().default_value(5U);
cmd.add_argument("--last-token-lifetime-days").scan<'u', uint32_t>().default_value(7U);
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
auto uid = Uid(parser.get<uint32_t>("uid"));
auto token = parser.present<String>("--token").value_or("");
auto generateNewToken = parser.get<bool>("--new");
auto invalidateExistedToken = parser.get<bool>("--invalidate-existed-tokens");
auto maxActiveTokens = parser.get<uint32_t>("--max-active-tokens");
auto tokenLifetimeExtendLength = Duration(std::chrono::days(parser.get<uint32_t>("--last-token-lifetime-days")));
ENSURE_USAGE(!token.empty() + generateNewToken == 1, "must set one of token and generate-token");
core::UserStore store;
auto handler = [&](kv::IReadWriteTransaction &txn) -> CoTryTask<meta::UserAttr> {
CO_RETURN_ON_ERROR(co_await ensureAdmin(txn, store, env.userInfo.token));
co_return co_await store
.setUserToken(txn, uid, token, invalidateExistedToken, maxActiveTokens, tokenLifetimeExtendLength);
};
auto commitRes = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadWriteTransaction(), std::move(handler));
CO_RETURN_ON_ERROR(commitRes);
printUserAttr(output, uid, *commitRes);
co_return output;
}
};
struct UserStatHandler {
static auto getParser() {
argparse::ArgumentParser cmd("user-stat");
cmd.add_argument("uid").scan<'u', uint32_t>();
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
auto uid = Uid(parser.get<uint32_t>("uid"));
core::UserStore store;
auto handler = [&](kv::IReadOnlyTransaction &txn) -> CoTryTask<meta::UserAttr> {
CO_RETURN_ON_ERROR(co_await ensureAdmin(txn, store, env.userInfo.token, uid));
auto fetchResult = co_await store.getUser(txn, uid);
CO_RETURN_ON_ERROR(fetchResult);
if (!fetchResult->has_value()) {
co_return makeError(MetaCode::kNotFound, fmt::format("Uid {} not found", uid.toUnderType()));
}
co_return **fetchResult;
};
auto res = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadonlyTransaction(), std::move(handler));
CO_RETURN_ON_ERROR(res);
printUserAttr(output, uid, *res);
co_return output;
}
};
struct UserListHandler {
static auto getParser() {
argparse::ArgumentParser cmd("user-list");
cmd.add_argument("--json").default_value(false).implicit_value(true);
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
core::UserStore store;
auto handler = [&](kv::IReadOnlyTransaction &txn) -> CoTryTask<std::vector<flat::UserAttr>> {
CO_RETURN_ON_ERROR(co_await ensureAdmin(txn, store, env.userInfo.token));
co_return co_await store.listUsers(txn);
};
auto res = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadonlyTransaction(), std::move(handler));
CO_RETURN_ON_ERROR(res);
if (parser.get<bool>("--json")) {
struct UserAttrLite {
SERDE_STRUCT_FIELD(name, std::string());
SERDE_STRUCT_FIELD(gid, flat::Gid());
SERDE_STRUCT_FIELD(groups, std::vector<flat::Gid>());
SERDE_STRUCT_FIELD(uid, flat::Uid());
};
auto users = transformTo<std::vector>(std::span(res->begin(), res->end()), [](const flat::UserAttr &attr) {
return UserAttrLite{
.name = attr.name,
.gid = attr.gid,
.groups = attr.groups,
.uid = attr.uid,
};
});
output.push_back({serde::toJsonString(users, false, true)});
} else {
output.push_back({"Uid", "Name", "Token", "IsRoot", "IsAdmin", "Gids"});
for (const auto &attr : *res) {
std::vector<String> row;
row.push_back(std::to_string(attr.uid));
row.push_back(attr.name);
row.push_back(attr.token);
row.push_back(attr.root ? "true" : "false");
row.push_back(attr.admin ? "true" : "false");
row.push_back(fmt::format("{}", fmt::join(attr.groups, ", ")));
output.push_back(std::move(row));
}
}
co_return output;
}
};
void printUserInfo(Dispatcher::OutputTable &output, const flat::UserInfo &userInfo) {
output.push_back({"Uid", std::to_string(userInfo.uid)});
output.push_back({"Gid", std::to_string(userInfo.gid)});
auto groups = transformTo<std::vector>(std::span{userInfo.groups.begin(), userInfo.groups.size()},
[](flat::Gid gid) { return gid.toUnderType(); });
output.push_back({"Groups", fmt::format("[{}]", fmt::join(groups, ", "))});
output.push_back({"Token", userInfo.token});
}
struct UserSwitchHandler {
static auto getParser() {
argparse::ArgumentParser cmd("user-switch");
cmd.add_argument("-u", "--uid").help("-1 means geteuid").scan<'i', int64_t>();
cmd.add_argument("-g", "--gid").help("-1 means getegid").scan<'i', int64_t>();
cmd.add_argument("-t", "--token");
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
auto u = parser.present<int64_t>("-u");
auto g = parser.present<int64_t>("-g");
auto t = parser.present<String>("-t");
if (u) env.userInfo.uid = flat::Uid(*u == -1 ? geteuid() : *u);
if (g) env.userInfo.gid = flat::Gid(*g == -1 ? getegid() : *g);
if (t) env.userInfo.token = *t;
printUserInfo(output, env.userInfo);
co_return output;
}
};
struct CurrentUserHandler {
static auto getParser() {
argparse::ArgumentParser cmd("current-user");
return cmd;
}
static CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable output;
printUserInfo(output, env.userInfo);
co_return output;
}
};
} // namespace
CoTryTask<void> registerAdminUserCtrlHandler(Dispatcher &dispatcher) {
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<UserAddHandler>());
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<UserRemoveHandler>());
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<UserSetHandler>());
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<UserSetTokenHandler>());
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<UserStatHandler>());
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<UserListHandler>());
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<UserSwitchHandler>());
CO_RETURN_ON_ERROR(co_await dispatcher.registerHandler<CurrentUserHandler>());
co_return Void{};
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerAdminUserCtrlHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,137 @@
#include "client/cli/admin/Bench.h"
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <folly/Random.h>
#include <folly/executors/CPUThreadPoolExecutor.h>
#include <folly/executors/GlobalExecutor.h>
#include <folly/experimental/coro/BlockingWait.h>
#include <folly/experimental/coro/Collect.h>
#include <folly/experimental/coro/Invoke.h>
#include <fstream>
#include <sstream>
#include "AdminEnv.h"
#include "client/cli/admin/FileWrapper.h"
#include "client/cli/admin/Layout.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "client/storage/TargetSelection.h"
#include "common/serde/Serde.h"
#include "common/utils/Duration.h"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("bench");
parser.add_argument("path");
parser.add_argument("--rank").default_value(uint32_t{0}).scan<'u', uint32_t>();
parser.add_argument("--timeout").default_value(uint32_t{1}).scan<'u', uint32_t>();
parser.add_argument("--coroutines").default_value(uint32_t{1}).scan<'u', uint32_t>();
parser.add_argument("--seconds").default_value(uint32_t{60}).scan<'u', uint32_t>();
parser.add_argument("--remove").default_value(false).implicit_value(true);
return parser;
}
Result<Void> check(const Path &src, uint32_t expected, storage::ChecksumInfo checksum) {
if (checksum.value != expected) {
auto msg = fmt::format("check {} failed {:08x} != {:08x}", src, checksum.value, expected);
XLOG(ERR, msg);
return makeError(StorageClientCode::kChecksumMismatch, std::move(msg));
}
return Void{};
}
CoTryTask<Void> writeFileAndCheck(AdminEnv &env,
const Path &path,
std::string_view str,
uint32_t repeat,
Duration timeout,
uint32_t crc32c) {
auto openResult = co_await FileWrapper::openOrCreateFile(env, path, true);
CO_RETURN_AND_LOG_ON_ERROR(openResult);
auto &file = *openResult;
boost::iostreams::stream<boost::iostreams::basic_array_source<char>> stream(str.begin(), str.size());
auto writeResult = co_await file.writeFile(stream, str.size(), 0, 10_s);
CO_RETURN_AND_LOG_ON_ERROR(writeResult);
CO_RETURN_AND_LOG_ON_ERROR(check(path, crc32c, *writeResult));
std::ofstream out("/dev/null");
auto readResult =
co_await file.readFile(out, str.size(), 0, true, false, storage::client::TargetSelectionMode::HeadTarget);
CO_RETURN_AND_LOG_ON_ERROR(readResult);
CO_RETURN_AND_LOG_ON_ERROR(check(path, crc32c, *readResult));
readResult =
co_await file.readFile(out, str.size(), 0, true, false, storage::client::TargetSelectionMode::TailTarget);
CO_RETURN_AND_LOG_ON_ERROR(readResult);
CO_RETURN_AND_LOG_ON_ERROR(check(path, crc32c, *readResult));
co_return Void{};
}
CoTryTask<Dispatcher::OutputTable> handleWriteFile(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
// 1. parse arguments.
Path src = parser.get<std::string>("path");
auto rank = parser.get<uint32_t>("--rank");
auto timeout = 1_s * parser.get<uint32_t>("--timeout");
auto coroutines = parser.get<uint32_t>("--coroutines");
auto deadline = RelativeTime::now() + 1_s * parser.get<uint32_t>("--seconds");
auto remove = parser.get<bool>("--remove");
// 2. prepare datas.
std::vector<std::string> datas;
std::vector<uint32_t> crc32cList;
for (auto i = 0; i < 16; ++i) {
datas.emplace_back(std::string(128_MB, 'A' + i));
auto checksum = storage::ChecksumInfo::create(storage::ChecksumType::CRC32C,
reinterpret_cast<const uint8_t *>(datas.back().data()),
datas.back().length());
crc32cList.emplace_back(checksum.value);
}
// 3. write and check.
std::vector<CoTryTask<Void>> total;
total.reserve(coroutines);
for (auto i = 0u; i < coroutines; ++i) {
total.push_back(folly::coro::co_invoke([&, i]() -> CoTryTask<Void> {
auto path = src / fmt::format("{:03}.{:03}", rank, i);
while (RelativeTime::now() <= deadline) {
auto idx = folly::Random::rand32() % datas.size();
CO_RETURN_AND_LOG_ON_ERROR(co_await writeFileAndCheck(env, path, datas[idx], 1, timeout, crc32cList[idx])
.scheduleOn(folly::getGlobalCPUExecutor()));
if (remove) {
CO_RETURN_AND_LOG_ON_ERROR(
co_await env.metaClientGetter()->remove(env.userInfo, env.currentDirId, path, false));
}
}
co_return Void{};
}));
}
auto results = co_await folly::coro::collectAllRange(std::move(total));
for (auto result : results) {
CO_RETURN_AND_LOG_ON_ERROR(result);
}
co_return table;
}
} // namespace
CoTryTask<void> registerBenchHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleWriteFile);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerBenchHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1 @@
target_add_lib(admin-cli mgmtd meta common-cli mgmtd-client meta-client storage-client core-client kv apache_arrow_static chunk_engine)

View File

@@ -0,0 +1,75 @@
#include "Chdir.h"
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "scn/tuple_return/tuple_return.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("cd");
parser.add_argument("-L").default_value(false).implicit_value(true);
parser.add_argument("--inode").default_value(false).implicit_value(true);
parser.add_argument("path");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleChdir(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
// TODO: now cd can't correctly handle many cases e.g.:
// - path contains symlink.
// - and so on.
Dispatcher::OutputTable table;
auto pathstr = parser.get<std::string>("path");
Path path(pathstr);
auto inodeid = env.currentDirId;
auto metaClient = env.metaClientGetter();
bool followLastSymlink = parser.get<bool>("-L");
bool byinode = parser.get<bool>("--inode");
Result<meta::Inode> res = makeError(MetaCode::kFoundBug);
if (!byinode) {
inodeid = meta::InodeId::root();
if (!path.is_absolute()) {
inodeid = env.currentDirId;
}
res = co_await metaClient->stat(env.userInfo, inodeid, path, true);
} else {
auto [r, v] = scn::scan_tuple<uint64_t>(pathstr, "{:i}");
if (!r) {
co_return makeError(StatusCode::kInvalidArg, "invalid inodeid {}", path);
}
inodeid = meta::InodeId(v);
path = ".";
res = co_await metaClient->stat(env.userInfo, inodeid, std::nullopt, true);
}
CO_RETURN_ON_ERROR(res);
const auto &inode = res.value();
if (inode.getType() != meta::InodeType::Directory) {
co_return makeError(MetaCode::kNotDirectory);
}
if (followLastSymlink || byinode) {
auto getRealPathRes = co_await metaClient->getRealPath(env.userInfo, inodeid, path, true);
CO_RETURN_ON_ERROR(getRealPathRes);
path = *getRealPathRes;
}
env.currentDirId = inode.id;
env.currentDir = path.native();
co_return table;
}
} // namespace
CoTryTask<void> registerChdirHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleChdir);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerChdirHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,169 @@
#include "Checksum.h"
#include <folly/executors/GlobalExecutor.h>
#include <folly/experimental/coro/BlockingWait.h>
#include <fstream>
#include <memory>
#define OPENSSL_SUPPRESS_DEPRECATED
#include <openssl/md5.h>
#include <string>
#include "AdminEnv.h"
#include "client/cli/admin/FileWrapper.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "common/serde/Serde.h"
#include "common/utils/Coroutine.h"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("checksum");
parser.add_argument("path").nargs(argparse::nargs_pattern::at_least_one);
parser.add_argument("--list").default_value(false).implicit_value(true);
parser.add_argument("--batch").default_value(uint32_t{8}).scan<'u', uint32_t>();
parser.add_argument("--md5").default_value(false).implicit_value(true);
parser.add_argument("--fillZero").default_value(false).implicit_value(true);
parser.add_argument("-o", "--output");
return parser;
}
struct ReadResult {
Path path;
Result<Void> result = makeError(Status::OK);
storage::ChecksumInfo checksum;
std::array<uint8_t, MD5_DIGEST_LENGTH> md5{};
};
CoTryTask<Void> checksumFile(AdminEnv &env, const Path &path, bool fillZero, bool md5Enabled, ReadResult &result) {
result.path = path;
auto openResult = co_await FileWrapper::openOrCreateFile(env, path, false);
CO_RETURN_AND_LOG_ON_ERROR(openResult);
auto &file = *openResult;
MD5_CTX md5;
int md5ret = MD5_Init(&md5);
if (UNLIKELY(md5ret != 1)) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("md5 init failed: {}", md5ret));
}
auto replicasNum = file.replicasNum();
CO_RETURN_AND_LOG_ON_ERROR(replicasNum);
for (auto i = 0u; i < *replicasNum; ++i) {
std::ofstream out("/dev/null");
auto readResult = co_await file.readFile(out,
file.length(),
0,
true,
false,
storage::client::TargetSelectionMode::ManualMode,
i == 0 && md5Enabled ? &md5 : nullptr,
fillZero,
false,
0);
CO_RETURN_AND_LOG_ON_ERROR(readResult);
if (i == 0) {
result.checksum = *readResult;
} else if (result.checksum != *readResult) {
co_return makeError(
StorageCode::kChecksumMismatch,
fmt::format("file checksum mismatch, path {}, head {}, now:{} {}", path, result.checksum, i, *readResult));
}
}
md5ret = MD5_Final(result.md5.data(), &md5);
if (UNLIKELY(md5ret != 1)) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("md5 final failed: {}", md5ret));
}
co_return Void{};
}
CoTask<void> callChecksumFile(AdminEnv &env, const Path &path, bool fillZero, bool md5Enabled, ReadResult &result) {
result.result =
co_await checksumFile(env, path, fillZero, md5Enabled, result).scheduleOn(folly::getGlobalCPUExecutor());
}
CoTryTask<Dispatcher::OutputTable> handleChecksum(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
// 1. parse arguments.
auto paths = parser.get<std::vector<std::string>>("path");
auto list = parser.get<bool>("--list");
auto batchSize = parser.get<uint32_t>("--batch");
auto md5Enabled = parser.get<bool>("--md5");
auto fillZero = parser.get<bool>("--fillZero");
auto output = parser.present<std::string>("--output");
// 2. prepare file list.
std::vector<Path> fileList;
for (auto &src : paths) {
if (list) {
std::ifstream listFile(src);
if (!listFile) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("open list file failed, {}", src));
}
std::string line;
while (std::getline(listFile, line)) {
if (!line.empty()) {
fileList.push_back(Path{line});
}
}
} else {
fileList.push_back(src);
}
}
// 3. read file.
std::ofstream outputFile;
if (output.has_value()) {
outputFile = std::ofstream{output.value()};
if (!outputFile) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("create output file {} failed", output.value()));
}
}
std::ostream &out = output.has_value() ? outputFile : std::cout;
std::vector<ReadResult> readResults;
readResults.reserve(batchSize);
std::vector<CoTask<void>> batch;
for (auto &filePath : fileList) {
readResults.emplace_back();
batch.push_back(callChecksumFile(env, filePath, fillZero, md5Enabled, readResults.back()));
if (batch.size() >= batchSize || std::addressof(filePath) == std::addressof(fileList.back())) {
co_await folly::coro::collectAllRange(std::move(batch));
for (auto &result : readResults) {
if (result.result.hasValue()) {
if (md5Enabled) {
out << fmt::format("{} {:02x}\n", result.path, fmt::join(result.md5, ""));
} else {
out << fmt::format("{} {:08X}\n", result.path, result.checksum.value);
}
} else {
out << fmt::format("{} {}\n", result.path, result.result.error());
}
}
readResults.clear();
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerChecksumHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleChecksum);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerChecksumHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,51 @@
#include "Create.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/admin/Layout.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "fbs/meta/Schema.h"
#include "fbs/mgmtd/ChainRef.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("create");
parser.add_argument("-p", "--perm");
parser.add_argument("path");
addLayoutArguments(parser);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleCreate(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto path = parser.get<std::string>("path");
auto layout = parseLayout(parser);
auto p = parser.present<String>("-p");
meta::Permission perm(0644);
if (p) {
auto [r, v] = scn::scan_tuple<uint32_t>(*p, "{:o}");
if (!r) co_return makeError(StatusCode::kInvalidArg, fmt::format("invalid permission format: {}", r.error().msg()));
perm = meta::Permission(v);
}
auto res = co_await env.metaClientGetter()
->create(env.userInfo, env.currentDirId, Path(path), std::nullopt, perm, 0, layout);
CO_RETURN_ON_ERROR(res);
co_return table;
}
} // namespace
CoTryTask<void> registerCreateHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleCreate);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerCreateHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,81 @@
#include "CreateRange.h"
#include <folly/experimental/coro/Collect.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("create-range");
parser.add_argument("prefix");
parser.add_argument("inclusive_start").scan<'i', int64_t>();
parser.add_argument("exclusive_end").scan<'i', int64_t>();
parser.add_argument("-c", "--concurrency").default_value(1).scan<'i', int>();
return parser;
}
CoTask<Dispatcher::OutputTable> handleCreateSubRange(AdminEnv &env, const String &prefix, int64_t start, int64_t n) {
Dispatcher::OutputTable table;
for (auto i = 0; i < n; ++i) {
auto path = fmt::format("{}{}", prefix, start + i);
auto res = co_await env.metaClientGetter()
->create(env.userInfo, env.currentDirId, Path(path), std::nullopt, meta::Permission(0777), 0);
if (res.hasError()) {
table.push_back({fmt::format("Error at {}", start + i), res.error().describe()});
}
}
co_return std::move(table);
}
CoTryTask<Dispatcher::OutputTable> handleCreateRange(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
auto prefix = parser.get<std::string>("prefix");
auto inclusiveStart = parser.get<int64_t>("inclusive_start");
auto exclusiveEnd = parser.get<int64_t>("exclusive_end");
auto concurrency = parser.get<int>("-c");
auto total = exclusiveEnd - inclusiveStart;
auto every = total / concurrency;
auto remain = total % concurrency;
std::vector<CoTask<Dispatcher::OutputTable>> tasks;
auto start = inclusiveStart;
for (auto i = 0; i < remain; ++i) {
tasks.push_back(handleCreateSubRange(env, prefix, start, every + 1));
start += every + 1;
}
for (auto i = remain; i < concurrency; ++i) {
tasks.push_back(handleCreateSubRange(env, prefix, start, every));
start += every;
}
auto res = co_await folly::coro::collectAllRange(std::move(tasks));
auto succeeded = total;
Dispatcher::OutputTable table;
for (auto &t : res) {
for (auto &r : t) {
table.push_back(std::move(r));
}
succeeded -= t.size();
}
table.push_back({"Succeeded", std::to_string(succeeded)});
table.push_back({"Failed", std::to_string(total - succeeded)});
co_return table;
}
} // namespace
CoTryTask<void> registerCreateRangeHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleCreateRange);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerCreateRangeHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,83 @@
#include "CreateTarget.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/RapidCsv.h"
#include "common/utils/Result.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("create-target");
parser.add_argument("--node-id").scan<'u', flat::NodeId::UnderlyingType>().required();
parser.add_argument("--disk-index").scan<'u', uint32_t>().required();
parser.add_argument("--target-id").scan<'u', flat::TargetId::UnderlyingType>().required();
parser.add_argument("--chain-id").scan<'u', flat::ChainId::UnderlyingType>().required();
parser.add_argument("--add-chunk-size").default_value(false).implicit_value(true);
parser.add_argument("--chunk-size").nargs(argparse::nargs_pattern::any);
parser.add_argument("--use-new-chunk-engine").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleCreateTarget(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto nodeId = flat::NodeId(parser.get<flat::NodeId::UnderlyingType>("--node-id"));
storage::CreateTargetReq req;
req.targetId = flat::TargetId(parser.get<flat::TargetId::UnderlyingType>("--target-id"));
req.chainId = flat::ChainId(parser.get<flat::ChainId::UnderlyingType>("--chain-id"));
req.diskIndex = parser.get<uint32_t>("--disk-index");
req.addChunkSize = parser.get<bool>("--add-chunk-size");
req.onlyChunkEngine = parser.get<bool>("--use-new-chunk-engine");
std::vector<Size> chunkSizeList;
for (const auto &size : parser.get<std::vector<std::string>>("--chunk-size")) {
auto sizeResult = Size::from(size);
CO_RETURN_AND_LOG_ON_ERROR(sizeResult);
chunkSizeList.push_back(*sizeResult);
}
if (!chunkSizeList.empty()) {
req.chunkSizeList = std::move(chunkSizeList);
}
CO_RETURN_AND_LOG_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
if (routingInfo == nullptr || routingInfo->raw() == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto chainInfo = routingInfo->getChain(req.chainId);
if (chainInfo && chainInfo->targets.size() > 1) {
// chain info exists and the number of replicas is greater than one.
for (auto target : chainInfo->targets) {
if (target.targetId == req.targetId && target.publicState == flat::PublicTargetState::LASTSRV) {
co_return makeError(StorageClientCode::kRoutingError,
fmt::format("creation of this target is prohibited {}", target));
}
}
}
auto client = env.storageClientGetter();
auto res = co_await client->createTarget(nodeId, req);
CO_RETURN_ON_ERROR(res);
table.push_back({fmt::format("Create target {} on disk {} of {} succeeded",
req.targetId.toUnderType(),
req.diskIndex,
nodeId.toUnderType())});
co_return table;
}
} // namespace
CoTryTask<void> registerCreateTargetHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleCreateTarget);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerCreateTargetHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,80 @@
#include "CreateTargets.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/RapidCsv.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("create-targets");
parser.add_argument("--node-id").scan<'u', flat::NodeId::UnderlyingType>().required();
parser.add_argument("--disk-index").scan<'u', uint32_t>().nargs(argparse::nargs_pattern::at_least_one);
parser.add_argument("--allow-existing-target").default_value(false).implicit_value(true);
parser.add_argument("--add-chunk-size").default_value(false).implicit_value(true);
parser.add_argument("--use-new-chunk-engine").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleCreateTarget(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto nodeId = flat::NodeId(parser.get<flat::NodeId::UnderlyingType>("--node-id"));
auto diskIndexes = parser.get<std::vector<uint32_t>>("--disk-index");
auto diskIndexSet = std::set<uint32_t>(diskIndexes.begin(), diskIndexes.end());
auto allowExistingTarget = parser.get<bool>("--allow-existing-target");
auto addChunkSize = parser.get<bool>("--add-chunk-size");
auto onlyChunkEngine = parser.get<bool>("--use-new-chunk-engine");
CO_RETURN_AND_LOG_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto rawRoutingInfo = routingInfo->raw();
if (routingInfo == nullptr || rawRoutingInfo == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
for (auto &[targetId, targetInfo] : rawRoutingInfo->targets) {
if (targetInfo.nodeId == nodeId && targetInfo.diskIndex && diskIndexSet.contains(*targetInfo.diskIndex)) {
if (targetInfo.publicState == flat::PublicTargetState::LASTSRV) {
auto chainInfo = routingInfo->getChain(targetInfo.chainId);
if (chainInfo->targets.size() > 1) {
std::cout << fmt::format("creation of this target is prohibited {}, skip\n", targetInfo);
continue;
}
}
storage::CreateTargetReq req;
req.targetId = targetId;
req.chainId = targetInfo.chainId;
req.diskIndex = *targetInfo.diskIndex;
req.allowExistingTarget = allowExistingTarget;
req.addChunkSize = addChunkSize;
req.onlyChunkEngine = onlyChunkEngine;
auto client = env.storageClientGetter();
auto res = co_await client->createTarget(nodeId, req);
CO_RETURN_ON_ERROR(res);
std::cout << fmt::format("Create target {} on disk {} of {} succeeded\n",
req.targetId.toUnderType(),
req.diskIndex,
nodeId.toUnderType());
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerCreateTargetsHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleCreateTarget);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerCreateTargetsHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,36 @@
#include "DecodeUserToken.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "core/user/UserToken.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("decode-user-token");
parser.add_argument("token");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleDecodeUserToken(IEnv &,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
Dispatcher::OutputTable table;
ENSURE_USAGE(args.empty());
auto token = parser.get<std::string>("token");
auto res = core::decodeUserToken(token);
CO_RETURN_ON_ERROR(res);
auto [uid, ts] = *res;
table.push_back({"Uid", fmt::format("{}", uid)});
table.push_back({"Timestamp", fmt::format("{}", ts)});
co_return table;
}
} // namespace
CoTryTask<void> registerDecodeUserTokenHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleDecodeUserToken);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerDecodeUserTokenHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,77 @@
#include "DropUserCache.h"
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/StringUtils.h"
#include "stubs/MetaService/MetaServiceStub.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("drop-user-cache");
parser.add_argument("-u", "--uid").scan<'u', uint32_t>();
parser.add_argument("-a", "--all").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto u = parser.present<uint32_t>("-u");
auto all = parser.get<bool>("-a");
std::optional<flat::Uid> uid;
if (u) uid = flat::Uid(*u);
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo()->raw();
auto req = meta::DropUserCacheReq(uid, all);
table.push_back({"NodeId", "Status", "Desc"});
auto client = env.clientGetter();
for (const auto &[nid, ni] : routingInfo->nodes) {
if (ni.type != flat::NodeType::META) continue;
if (ni.status != flat::NodeStatus::HEARTBEAT_CONNECTED) {
table.push_back({std::to_string(nid), "Skip", fmt::format("Status is {}", toStringView(ni.status))});
continue;
}
auto addrs = ni.extractAddresses("MetaSerde");
if (addrs.empty()) {
table.push_back({std::to_string(nid), "Skip", "Address of MetaSerde not found"});
continue;
}
auto result = co_await [&]() -> CoTask<std::vector<Status>> {
std::vector<Status> rets;
for (auto addr : addrs) {
auto ctx = client->serdeCtx(addr);
auto stub = meta::MetaServiceStub<serde::ClientContext>(ctx);
auto ret = co_await stub.dropUserCache(req, {});
if (!ret.hasError()) co_return std::vector<Status>{};
rets.push_back(std::move(ret.error()));
}
co_return rets;
}();
if (result.empty()) {
table.push_back({std::to_string(nid), "Succeed", ""});
} else {
table.push_back({std::to_string(nid), "Failed", fmt::format("[{}]", fmt::join(result, ", "))});
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerDropUserCacheHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerDropUserCacheHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,53 @@
#include "DumpChainTable.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("dump-chain-table");
parser.add_argument("tableId").scan<'u', uint32_t>();
parser.add_argument("-v", "--version").scan<'u', uint32_t>();
parser.add_argument("csv-file-path");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleDumpChainTable(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto tableId = flat::ChainTableId(parser.get<uint32_t>("tableId"));
auto tableVer = flat::ChainTableVersion(parser.present<uint32_t>("-v").value_or(0));
auto csvFilePath = parser.get<std::string>("csv-file-path");
std::ofstream of(csvFilePath);
of.exceptions(std::ofstream::failbit | std::ofstream::badbit);
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
XLOGF(DBG, "DumpChainTable routingInfo:{}", serde::toJsonString(*routingInfo->raw()));
auto *t = routingInfo->raw()->getChainTable(tableId, tableVer);
if (!t) co_return makeError(StatusCode::kInvalidArg, fmt::format("{}@{} not found", tableId, tableVer));
tableVer = t->chainTableVersion;
of << "ChainId\n";
for (auto cid : t->chains) {
of << cid.toUnderType() << "\n";
}
table.push_back({fmt::format("Dump {} of {} to {} succeeded", tableId, tableVer, csvFilePath)});
co_return table;
}
} // namespace
CoTryTask<void> registerDumpChainTableHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleDumpChainTable);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerDumpChainTableHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,59 @@
#include "DumpChains.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("dump-chains");
parser.add_argument("csv-file-path-prefix");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleDumpChains(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto csvFilePathPrefix = parser.get<std::string>("csv-file-path-prefix");
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
XLOGF(DBG, "DumpChains routingInfo:{}", serde::toJsonString(*routingInfo->raw()));
robin_hood::unordered_map<size_t, std::vector<flat::ChainId>> chainsByReplicaCount;
for (const auto &[cid, ci] : routingInfo->raw()->chains) {
chainsByReplicaCount[ci.targets.size()].push_back(cid);
}
for (const auto &[rc, chains] : chainsByReplicaCount) {
auto path = fmt::format("{}.{}", csvFilePathPrefix, rc);
std::ofstream of(path);
of.exceptions(std::ofstream::failbit | std::ofstream::badbit);
of << "ChainId";
for (size_t i = 0; i < rc; ++i) of << ",TargetId";
of << "\n";
for (auto cid : chains) {
const auto &ci = routingInfo->raw()->chains[cid];
of << cid.toUnderType();
for (const auto &ti : ci.targets) of << "," << ti.targetId.toUnderType();
of << "\n";
}
table.push_back({fmt::format("Dump {} chains of {} replicas to {} succeeded", chains.size(), rc, path)});
}
co_return table;
}
} // namespace
CoTryTask<void> registerDumpChainsHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleDumpChains);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerDumpChainsHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,262 @@
#include "DumpChunkMeta.h"
#include <folly/logging/xlog.h>
#include <vector>
#include "AdminEnv.h"
#include "analytics/SerdeObjectReader.h"
#include "analytics/SerdeObjectWriter.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/IEnv.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("dump-chunkmeta");
parser.add_argument("-c", "--chain-ids").nargs(argparse::nargs_pattern::any).scan<'u', uint32_t>();
parser.add_argument("-m", "--chunkmeta-dir").default_value(std::string{"chunkmeta"});
parser.add_argument("-q", "--parquet-format").default_value(false).implicit_value(true);
parser.add_argument("-h", "--only-head").default_value(false).implicit_value(true);
parser.add_argument("-p", "--parallel").default_value(uint32_t{32}).scan<'u', uint32_t>();
return parser;
}
CoTryTask<Dispatcher::OutputTable> dumpChunkMeta(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
const auto &chainIds = parser.get<std::vector<uint32_t>>("chain-ids");
const auto &chunkmetaDir = parser.get<std::string>("chunkmeta-dir");
const auto parallel = std::min(parser.get<uint32_t>("parallel"), std::thread::hardware_concurrency() / 2);
const auto &parquetFormat = parser.get<bool>("parquet-format");
const auto &onlyHead = parser.get<bool>("only-head");
if (boost::filesystem::exists(chunkmetaDir)) {
XLOGF(CRITICAL, "Output directory for chunk metadata already exists: {}", chunkmetaDir);
co_return makeError(StatusCode::kInvalidArg);
}
boost::system::error_code err{};
boost::filesystem::create_directories(chunkmetaDir, err);
if (UNLIKELY(err.failed())) {
XLOGF(CRITICAL, "Failed to create directory {}, error: {}", chunkmetaDir, err.message());
co_return makeError(StatusCode::kIOError);
}
XLOGF(CRITICAL, "Saving chunk metadata to directory: {}", chunkmetaDir);
CO_RETURN_AND_LOG_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto chainInfos = routingInfo->raw()->chains;
XLOGF(CRITICAL, "Found {} replication chains in {}", chainInfos.size(), routingInfo->raw()->routingInfoVersion);
std::map<flat::ChainId, flat::ChainInfo> sortedChainInfos;
for (const auto &[chainId, chainInfo] : chainInfos) sortedChainInfos.emplace(chainId, chainInfo);
std::set<uint32_t> selectedChainIds(chainIds.begin(), chainIds.end());
size_t numChainsToSave = selectedChainIds.empty() ? sortedChainInfos.size() : selectedChainIds.size();
std::atomic_size_t numTargetsSaved = 0;
auto dumpChunkMeta = [env, chunkmetaDir, parquetFormat, numChainsToSave, &numTargetsSaved](
const size_t chainIndex,
const flat::ChainId chainId,
const flat::TargetId targetId) -> CoTask<bool> {
XLOGF(INFO, "#{}/{} Getting chunk metadata from target: {}@{}", chainIndex, numChainsToSave, targetId, chainId);
auto chunkMetadata = co_await env.storageClientGetter()->getAllChunkMetadata(chainId, targetId);
if (!chunkMetadata) {
XLOGF(ERR,
"Failed to get chunk metadata from target: {}@{}, error: {}",
targetId,
chainId,
chunkMetadata.error());
co_return false;
}
time_t timestamp = UtcClock::secondsSinceEpoch();
ChunkMetaTable metadata{chainId, targetId, timestamp};
for (const auto &chunkmeta : *chunkMetadata) {
auto metaChunkId = meta::ChunkId::unpack(chunkmeta.chunkId.data());
metadata.chunks.push_back({timestamp, chainId, targetId, metaChunkId.inode(), chunkmeta});
}
XLOGF(INFO,
"#{}/{} Found {} chunks on target: {}@{}",
chainIndex,
numChainsToSave,
metadata.chunks.size(),
targetId,
chainId);
auto filePath =
Path(chunkmetaDir) / fmt::format("{}-{}.meta", uint32_t(metadata.chainId), uint64_t(metadata.targetId));
bool writeOk = parquetFormat ? metadata.dumpToParquetFile(filePath.replace_extension(".parquet"))
: metadata.dumpToFile(filePath);
numTargetsSaved += writeOk;
XLOGF_IF(WARN,
writeOk,
"#{}/{} Metadata of {} chunks on {}@{} saved to file: {}",
chainIndex,
numChainsToSave,
metadata.chunks.size(),
targetId,
chainId,
filePath);
co_return writeOk;
};
auto executor = std::make_unique<folly::CPUThreadPoolExecutor>(parallel);
std::vector<folly::coro::TaskWithExecutor<bool>> tasks;
size_t chainIndex = 0;
for (const auto &[chainId, chainInfo] : sortedChainInfos) {
if (!selectedChainIds.empty() && !selectedChainIds.count(uint32_t(chainId))) continue;
chainIndex++;
if (chainInfo.targets.empty()) {
XLOGF(CRITICAL, "Empty list of targets on {}: {}", chainId, chainInfo);
co_return makeError(StorageClientCode::kRoutingError);
}
for (const auto &target : chainInfo.targets) {
if (target.publicState == flat::PublicTargetState::SERVING) {
if (parallel > 0) {
tasks.push_back(dumpChunkMeta(chainIndex, chainId, target.targetId)
.scheduleOn(folly::Executor::getKeepAliveToken(*executor)));
} else {
bool ok = co_await dumpChunkMeta(chainIndex, chainId, target.targetId);
if (!ok) co_return makeError(StatusCode::kIOError);
}
} else {
XLOGF(WARN, "Skip target {}@{} not in serving state: {}", target.targetId, chainId, target);
}
if (onlyHead) break;
}
}
if (parallel > 0 && !tasks.empty()) {
auto results = co_await folly::coro::collectAllWindowed(std::move(tasks), parallel);
if (!std::all_of(results.begin(), results.end(), [](bool ok) { return ok; })) {
XLOGF(CRITICAL, "Some of the chunkmeta dump tasks failed");
co_return makeError(StatusCode::kIOError);
}
}
XLOGF(CRITICAL,
"Chunk metadata on {} targets from {} chains saved to directory: {}",
numTargetsSaved.load(),
numChainsToSave,
chunkmetaDir);
co_return table;
}
} // namespace
bool ChunkMetaTable::dumpToFile(const Path &filePath, bool jsonFormat) const {
std::ofstream dumpFile{filePath};
if (UNLIKELY(!dumpFile)) {
XLOGF(ERR, "Failed to create file: {}", filePath);
return false;
}
std::string buffer;
if (jsonFormat) {
buffer = serde::toJsonString(*this, false /*sortKeys*/, true /*prettyFormatting*/);
} else {
auto bytes = serde::serializeBytes(*this);
buffer = std::string(bytes);
}
dumpFile.write(buffer.data(), buffer.size());
if (UNLIKELY(!dumpFile)) {
XLOGF(ERR, "Failed to write to file: {}", filePath);
return false;
}
return true;
}
bool ChunkMetaTable::loadFromFile(const Path &filePath, bool jsonFormat) {
std::ifstream inputFile{filePath, std::ios_base::binary};
if (UNLIKELY(!inputFile)) {
XLOGF(ERR, "Failed to open file: {}", filePath);
return false;
}
auto fileSize = static_cast<size_t>(boost::filesystem::file_size(filePath));
std::string buffer;
buffer.resize(fileSize, '\0');
inputFile.read(&buffer[0], fileSize);
if (buffer.size() != fileSize) {
XLOGF(ERR, "Read size {} not equal to file size {}, file: {}", buffer.size(), fileSize, filePath);
return false;
}
if (jsonFormat) {
auto result = serde::fromJsonString(*this, buffer);
return bool(result);
} else {
auto result = serde::deserialize(*this, buffer);
return bool(result);
}
}
bool ChunkMetaTable::dumpToParquetFile(const Path &filePath) const {
auto openRes = analytics::SerdeObjectWriter<ChunkMetaRow>::open(filePath);
if (!openRes) return false;
auto serdeWriter = std::move(*openRes);
for (const auto &chunkmeta : chunks) {
serdeWriter << chunkmeta;
if (!serdeWriter) break;
}
auto timestamp = UtcClock::secondsSinceEpoch();
serdeWriter << ChunkMetaRow{timestamp, chainId, targetId, meta::InodeId{}, storage::ChunkMeta{}};
return serdeWriter.ok();
}
bool ChunkMetaTable::loadFromParquetFile(const Path &filePath) {
auto openRes = analytics::SerdeObjectReader<ChunkMetaRow>::open(filePath);
if (!openRes) return false;
auto serdeReader = std::move(*openRes);
chunks.reserve(serdeReader.numRows());
timestamp = 0;
ChunkMetaRow chunkRow;
while (serdeReader >> chunkRow) {
chunks.push_back(chunkRow);
chainId = chunkRow.chainId;
targetId = chunkRow.targetId;
timestamp = std::max(timestamp, chunkRow.timestamp);
}
return serdeReader.ok();
}
CoTryTask<void> registerDumpChunkMetaHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, dumpChunkMeta);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,36 @@
#pragma once
#include <parquet/schema.h>
#include "common/serde/Serde.h"
#include "common/utils/Coroutine.h"
#include "fbs/meta/Schema.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
class ChunkMetaRow {
SERDE_STRUCT_FIELD(timestamp, std::time_t{});
SERDE_STRUCT_FIELD(chainId, storage::ChainId{});
SERDE_STRUCT_FIELD(targetId, storage::TargetId{});
SERDE_STRUCT_FIELD(inodeId, meta::InodeId{});
SERDE_STRUCT_FIELD(chunkmeta, storage::ChunkMeta{});
};
struct ChunkMetaTable {
SERDE_STRUCT_FIELD(chainId, storage::ChainId{});
SERDE_STRUCT_FIELD(targetId, storage::TargetId{});
SERDE_STRUCT_FIELD(timestamp, std::time_t{});
SERDE_STRUCT_FIELD(chunks, std::vector<ChunkMetaRow>{});
public:
bool dumpToFile(const Path &filePath, bool jsonFormat = false) const;
bool loadFromFile(const Path &filePath, bool jsonFormat = false);
bool dumpToParquetFile(const Path &filePath) const;
bool loadFromParquetFile(const Path &filePath);
};
static_assert(serde::Serializable<ChunkMetaTable>);
class Dispatcher;
CoTryTask<void> registerDumpChunkMetaHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,164 @@
#include "DumpDirEntries.h"
#include <algorithm>
#include <cstdint>
#include <folly/ScopeGuard.h>
#include <folly/logging/xlog.h>
#include <thread>
#include <vector>
#include "AdminEnv.h"
#include "analytics/SerdeObjectReader.h"
#include "analytics/SerdeObjectWriter.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/IEnv.h"
#include "client/cli/common/Utils.h"
#include "common/logging/LogHelper.h"
#include "common/utils/Utf8.h"
#include "fbs/meta/Schema.h"
#include "fmt/core.h"
#include "meta/event/Scan.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("dump-dentries");
parser.add_argument("-n", "--num-dentries-perfile").default_value(uint32_t{10'000'000}).scan<'u', uint32_t>();
parser.add_argument("-f", "--fdb-cluster-file").default_value(std::string{"./fdb.cluster"});
parser.add_argument("-d", "--dentry-dir").default_value(std::string{"dentries"});
parser.add_argument("-t", "--threads").default_value(uint32_t(4)).scan<'u', uint32_t>();
return parser;
}
CoTryTask<Dispatcher::OutputTable> dumpDirEntries(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
ENSURE_USAGE(env.mgmtdClientGetter);
const auto &fdbClusterFile = parser.get<std::string>("fdb-cluster-file");
const auto &numEntriesPerFile = parser.get<uint32_t>("num-dentries-perfile");
const auto &dentryDir = parser.get<std::string>("dentry-dir");
const auto threads = parser.get<uint32_t>("threads");
ENSURE_USAGE(threads > 0);
ENSURE_USAGE(!fdbClusterFile.empty());
ENSURE_USAGE(!dentryDir.empty());
ENSURE_USAGE(numEntriesPerFile > 0);
if (boost::filesystem::exists(dentryDir)) {
XLOGF(CRITICAL, "Output directory for directory entries already exists: {}", dentryDir);
co_return makeError(StatusCode::kInvalidArg, "output directory already exists");
}
Dispatcher::OutputTable table;
auto dumpRes =
co_await dumpDirEntriesFromFdb(fdbClusterFile, numEntriesPerFile, dentryDir, std::max(uint32_t(1), threads));
if (!dumpRes) co_return makeError(dumpRes.error());
co_return table;
}
} // namespace
CoTryTask<Void> dumpDirEntriesFromFdb(const std::string fdbClusterFile,
const uint32_t numEntriesPerDir,
const std::string dentryDir,
const uint32_t threads) {
boost::system::error_code err{};
boost::filesystem::create_directories(dentryDir, err);
if (UNLIKELY(err.failed())) {
XLOGF(CRITICAL, "Failed to create directory {}, error: {}", dentryDir, err.message());
co_return makeError(StatusCode::kIOError,
fmt::format("failed to create directory {}, error {}", dentryDir, err.message()));
}
XLOGF(CRITICAL, "Saving directory entries to directory: {}", dentryDir);
meta::server::MetaScan::Options options;
options.fdb_cluster_file = fdbClusterFile;
options.threads = 8;
options.coroutines = 32;
auto scan = std::make_unique<meta::server::MetaScan>(options);
auto exec = std::make_unique<folly::CPUThreadPoolExecutor>(16);
time_t timestamp = UtcClock::secondsSinceEpoch();
DirEntryTable dentryBatch{.timestamp = timestamp};
dentryBatch.entries.reserve(numEntriesPerDir);
size_t numEntriesSaved = 0;
std::atomic<size_t> running = 0;
auto dumpDirEntryTable = [&running, dentryDir](size_t numEntriesSaved,
const DirEntryTable dentryBatch) -> CoTask<bool> {
SCOPE_EXIT { running--; };
auto filePath = Path(dentryDir) / fmt::format("{}.parquet", numEntriesSaved);
bool writeOk = dentryBatch.dumpToParquetFile(filePath);
XLOGF_IF(WARN, writeOk, "{} directory entries saved to file: {}", dentryBatch.entries.size(), filePath);
co_return writeOk;
};
std::vector<folly::SemiFuture<bool>> tasks;
while (true) {
auto entries = scan->getDirEntries();
for (auto &entry : entries) {
dentryBatch.entries.push_back({timestamp, entry});
numEntriesSaved++;
}
bool fullBatch = dentryBatch.entries.size() >= numEntriesPerDir;
bool lastBatch = entries.empty() && !dentryBatch.entries.empty();
if (fullBatch || lastBatch) {
running++;
auto task = folly::coro::co_invoke(dumpDirEntryTable,
numEntriesSaved,
std::exchange(dentryBatch, DirEntryTable{.timestamp = timestamp}))
.scheduleOn(exec.get())
.start();
tasks.push_back(std::move(task));
dentryBatch.entries.reserve(numEntriesPerDir);
while (running >= threads) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
if (entries.empty()) break;
}
auto result = co_await folly::coro::collectAllRange(std::move(tasks));
auto succ = std::all_of(result.begin(), result.end(), [](auto v) { return v; });
if (!succ) {
co_return makeError(StatusCode::kIOError);
}
XLOGF(CRITICAL, "In total {} directory entries saved to directory: {}", numEntriesSaved, dentryDir);
co_return Void{};
}
bool DirEntryTable::dumpToParquetFile(const Path &filePath) const {
auto openRes = analytics::SerdeObjectWriter<DirEntryRow>::open(filePath);
if (!openRes) return false;
auto serdeWriter = std::move(*openRes);
for (const auto &entry : entries) {
utf8makevalid((utf8_int8_t *)entry.entry.name.c_str(), '?');
serdeWriter << entry;
if (!serdeWriter) break;
}
auto timestamp = UtcClock::secondsSinceEpoch();
serdeWriter << DirEntryRow{timestamp, meta::DirEntry{}};
return serdeWriter.ok();
}
CoTryTask<void> registerDumpDirEntriesHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, dumpDirEntries);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,30 @@
#pragma once
#include <cstdint>
#include <vector>
#include "common/serde/Serde.h"
#include "fbs/meta/Schema.h"
namespace hf3fs::client::cli {
struct DirEntryRow {
SERDE_STRUCT_FIELD(timestamp, std::time_t{});
SERDE_STRUCT_FIELD(entry, meta::DirEntry{});
};
struct DirEntryTable {
SERDE_STRUCT_FIELD(timestamp, std::time_t{});
SERDE_STRUCT_FIELD(entries, std::vector<DirEntryRow>());
public:
bool dumpToParquetFile(const Path &filePath) const;
};
static_assert(serde::Serializable<DirEntryTable>);
CoTryTask<Void> dumpDirEntriesFromFdb(const std::string fdbClusterFile,
const uint32_t numEntriesPerDir,
const std::string dentryDir,
const uint32_t threads);
class Dispatcher;
CoTryTask<void> registerDumpDirEntriesHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,341 @@
#include "DumpInodes.h"
#include <algorithm>
#include <cstdint>
#include <folly/executors/CPUThreadPoolExecutor.h>
#include <folly/experimental/coro/Collect.h>
#include <folly/experimental/coro/CurrentExecutor.h>
#include <folly/experimental/coro/Invoke.h>
#include <folly/futures/Future.h>
#include <folly/logging/xlog.h>
#include <memory>
#include <utility>
#include <vector>
#include "AdminEnv.h"
#include "analytics/SerdeObjectReader.h"
#include "analytics/SerdeObjectWriter.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/IEnv.h"
#include "client/cli/common/Utils.h"
#include "common/logging/LogHelper.h"
#include "common/utils/Result.h"
#include "common/utils/Utf8.h"
#include "meta/event/Scan.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("dump-inodes");
parser.add_argument("-n", "--num-inodes-perfile").default_value(uint32_t{10'000'000}).scan<'u', uint32_t>();
parser.add_argument("-f", "--fdb-cluster-file").default_value(std::string{"./fdb.cluster"});
parser.add_argument("-i", "--inode-dir").default_value(std::string{"inodes"});
parser.add_argument("-q", "--parquet-format").default_value(false).implicit_value(true);
parser.add_argument("-a", "--all-inodes").default_value(false).implicit_value(true);
parser.add_argument("-t", "--threads").default_value(uint32_t(4)).scan<'u', uint32_t>();
return parser;
}
CoTryTask<Dispatcher::OutputTable> dumpInodes(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
ENSURE_USAGE(env.mgmtdClientGetter);
const auto &fdbClusterFile = parser.get<std::string>("fdb-cluster-file");
const auto &numInodesPerFile = parser.get<uint32_t>("num-inodes-perfile");
const auto &inodeDir = parser.get<std::string>("inode-dir");
const auto &parquetFormat = parser.get<bool>("parquet-format");
const auto &allInodes = parser.get<bool>("all-inodes");
const auto &threads = parser.get<uint32_t>("threads");
ENSURE_USAGE(threads > 0);
ENSURE_USAGE(!fdbClusterFile.empty());
if (boost::filesystem::exists(inodeDir)) {
XLOGF(CRITICAL, "Output directory for inodes already exists: {}", inodeDir);
co_return makeError(StatusCode::kInvalidArg);
}
Dispatcher::OutputTable table;
auto dumpRes = co_await dumpInodesFromFdb(fdbClusterFile,
numInodesPerFile,
inodeDir,
parquetFormat,
allInodes,
std::max((uint32_t)1, threads));
if (!dumpRes) co_return makeError(dumpRes.error());
co_return table;
}
} // namespace
CoTryTask<Void> dumpInodesFromFdb(const std::string fdbClusterFile,
const uint32_t numInodesPerFile,
const std::string inodeDir,
const bool parquetFormat,
const bool dumpAllInodes,
const uint32_t threads) {
boost::system::error_code err{};
boost::filesystem::create_directories(inodeDir, err);
if (UNLIKELY(err.failed())) {
XLOGF(CRITICAL, "Failed to create directory {}, error: {}", inodeDir, err.message());
co_return makeError(StatusCode::kIOError);
}
XLOGF(CRITICAL, "Saving inodes to directory: {}", inodeDir);
meta::server::MetaScan::Options options;
options.fdb_cluster_file = fdbClusterFile;
options.threads = 8;
options.coroutines = 32;
auto scan = std::make_unique<meta::server::MetaScan>(options);
auto exec = std::make_unique<folly::CPUThreadPoolExecutor>(16);
time_t timestamp = UtcClock::secondsSinceEpoch();
InodeTable inodeBatch{.timestamp = timestamp};
inodeBatch.inodes.reserve(numInodesPerFile);
size_t numInodesSaved = 0;
std::atomic<size_t> running = 0;
auto dumpInodeTable = [&running, inodeDir, parquetFormat](size_t numInodesSaved,
const InodeTable inodeBatch) -> CoTask<bool> {
SCOPE_EXIT { running--; };
auto filePath = Path(inodeDir) / fmt::format("{}.inodes", numInodesSaved);
bool writeOk = parquetFormat ? inodeBatch.dumpToParquetFile(filePath.replace_extension(".parquet"))
: inodeBatch.dumpToFile(filePath);
XLOGF_IF(WARN, writeOk, "{} inodes saved to file: {}", inodeBatch.inodes.size(), filePath);
co_return writeOk;
};
std::vector<folly::SemiFuture<bool>> tasks;
while (true) {
auto inodes = scan->getInodes();
for (const auto &inode : inodes) {
if (inode.isFile() || dumpAllInodes) {
inodeBatch.inodes.push_back({timestamp, inode});
numInodesSaved++;
}
}
bool fullBatch = inodeBatch.inodes.size() >= numInodesPerFile;
bool lastBatch = inodes.empty() && !inodeBatch.inodes.empty();
if (fullBatch || lastBatch) {
running++;
auto task = folly::coro::co_invoke(dumpInodeTable,
numInodesSaved,
std::exchange(inodeBatch, InodeTable{.timestamp = timestamp}))
.scheduleOn(exec.get())
.start();
tasks.push_back(std::move(task));
inodeBatch.inodes.reserve(numInodesPerFile);
while (running >= threads) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
if (inodes.empty()) break;
}
auto result = co_await folly::coro::collectAllRange(std::move(tasks));
auto succ = std::all_of(result.begin(), result.end(), [](auto v) { return v; });
if (!succ) {
co_return makeError(StatusCode::kIOError);
}
XLOGF(CRITICAL, "In total {} inodes saved to directory: {}", numInodesSaved, inodeDir);
co_return Void{};
}
std::vector<Path> listFilesFromPath(const Path path) {
std::vector<Path> filePaths;
if (boost::filesystem::is_directory(path)) {
for (boost::filesystem::directory_iterator iter{path}; iter != boost::filesystem::directory_iterator(); iter++) {
if (boost::filesystem::is_regular_file(iter->path())) {
filePaths.push_back(iter->path());
}
}
} else {
filePaths.push_back(path);
}
return filePaths;
}
CoTryTask<robin_hood::unordered_set<meta::InodeId>> loadInodeFromFiles(
const Path inodePath,
const robin_hood::unordered_set<meta::InodeId> &inodeIdsToPeek,
const uint32_t parallel,
const bool parquetFormat,
std::time_t *inodeDumpTime) {
std::mutex inodeIdsMutex;
size_t inodeFilesLoaded = 0;
robin_hood::unordered_set<meta::InodeId> uniqInodeIds(10'000'000);
std::time_t inodeFileMinDumpTime(UtcClock::secondsSinceEpoch());
auto executor = std::make_unique<folly::CPUThreadPoolExecutor>(parallel);
std::vector<Path> inodeFilePaths = listFilesFromPath(inodePath);
CountDownLatch<folly::fibers::Baton> loadInodesDone(inodeFilePaths.size());
for (size_t inodeFileIndex = 0; inodeFileIndex < inodeFilePaths.size(); inodeFileIndex++) {
executor->add([&, inodeFileIndex, inodeFilePath = inodeFilePaths[inodeFileIndex]]() {
auto guard = folly::makeGuard([&loadInodesDone]() { loadInodesDone.countDown(); });
InodeTable inodeBatch;
bool ok = parquetFormat ? inodeBatch.loadFromParquetFile(inodeFilePath) : inodeBatch.loadFromFile(inodeFilePath);
if (!ok) {
XLOGF(FATAL, "Failed to load file: {}", inodeFilePath);
return false;
}
std::vector<meta::InodeId> inodeIdVec;
inodeIdVec.reserve(inodeBatch.inodes.size());
for (const auto &inodeRow : inodeBatch.inodes) {
inodeIdVec.push_back(inodeRow.inode.id);
if (!inodeIdsToPeek.empty() && inodeIdsToPeek.count(inodeRow.inode.id)) {
XLOGF(CRITICAL, "Found inode {}: {}", inodeRow.inode.id, inodeRow.inode);
}
}
{
std::scoped_lock lock(inodeIdsMutex);
inodeFileMinDumpTime = std::min(inodeBatch.timestamp, inodeFileMinDumpTime);
uniqInodeIds.insert(inodeIdVec.begin(), inodeIdVec.end());
inodeFilesLoaded++;
XLOGF(WARN,
"#{}/{} Loaded {} inodes from file: {}, total number of inodes: {}, timestamp: {:%c}",
inodeFileIndex + 1,
inodeFilePaths.size(),
inodeBatch.inodes.size(),
inodeFilePath,
uniqInodeIds.size(),
fmt::localtime(inodeBatch.timestamp));
}
return true;
});
}
co_await loadInodesDone.wait();
executor->join();
XLOGF(CRITICAL,
"Loaded {} inodes from {}/{} files in path: {}",
uniqInodeIds.size(),
inodeFilesLoaded,
inodeFilePaths.size(),
inodePath);
if (inodeFilesLoaded < inodeFilePaths.size()) {
XLOGF(FATAL,
"Failed to load {}/{} inode files in path: {}",
inodeFilePaths.size() - inodeFilesLoaded,
inodeFilePaths.size(),
inodePath);
co_return makeError(StatusCode::kIOError);
}
if (inodeDumpTime != nullptr) *inodeDumpTime = inodeFileMinDumpTime;
co_return uniqInodeIds;
}
bool InodeTable::dumpToFile(const Path &filePath) const {
std::ofstream dumpFile{filePath};
if (UNLIKELY(!dumpFile)) {
XLOGF(ERR, "Failed to create file: {}", filePath);
return false;
}
auto bytes = serde::serializeBytes(*this);
auto str = std::string_view{bytes};
dumpFile.write(str.data(), str.size());
if (UNLIKELY(!dumpFile)) {
XLOGF(ERR, "Failed to write to file: {}", filePath);
return false;
}
return true;
}
bool InodeTable::loadFromFile(const Path &filePath) {
std::ifstream inputFile{filePath, std::ios_base::binary};
if (UNLIKELY(!inputFile)) {
XLOGF(ERR, "Failed to open file: {}", filePath);
return false;
}
auto fileSize = static_cast<size_t>(boost::filesystem::file_size(filePath));
std::string str;
str.resize(fileSize, '\0');
inputFile.read(&str[0], fileSize);
if (str.size() != fileSize) {
XLOGF(ERR, "Read size {} not equal to file size {}, file: {}", str.size(), fileSize, filePath);
return false;
}
auto result = serde::deserialize(*this, str);
return bool(result);
}
bool InodeTable::dumpToParquetFile(const Path &filePath) const {
auto openRes = analytics::SerdeObjectWriter<InodeRow>::open(filePath);
if (!openRes) return false;
auto serdeWriter = std::move(*openRes);
for (const auto &inode : inodes) {
if (inode.inode.isDirectory()) {
utf8makevalid((utf8_int8_t *)inode.inode.asDirectory().name.c_str(), '?');
}
serdeWriter << inode;
if (!serdeWriter) break;
}
auto timestamp = UtcClock::secondsSinceEpoch();
serdeWriter << InodeRow{timestamp, meta::Inode{}};
return serdeWriter.ok();
}
bool InodeTable::loadFromParquetFile(const Path &filePath) {
auto openRes = analytics::SerdeObjectReader<InodeRow>::open(filePath);
if (!openRes) return false;
auto serdeReader = std::move(*openRes);
inodes.reserve(serdeReader.numRows());
timestamp = UtcClock::secondsSinceEpoch();
InodeRow inodeRow;
while (serdeReader >> inodeRow) {
inodes.push_back(inodeRow);
timestamp = std::min(timestamp, inodeRow.timestamp);
}
ERRLOGF_IF(INFO,
inodes.size() != serdeReader.numRows(),
"Loaded {} inodes from {} rows in file: {}",
inodes.size(),
serdeReader.numRows(),
filePath);
return serdeReader.ok();
}
CoTryTask<void> registerDumpInodesHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, dumpInodes);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,44 @@
#pragma once
#include "common/serde/Serde.h"
#include "common/utils/Coroutine.h"
#include "fbs/meta/Schema.h"
namespace hf3fs::client::cli {
struct InodeRow {
SERDE_STRUCT_FIELD(timestamp, std::time_t{});
SERDE_STRUCT_FIELD(inode, meta::Inode{});
};
struct InodeTable {
SERDE_STRUCT_FIELD(timestamp, std::time_t{});
SERDE_STRUCT_FIELD(inodes, std::vector<InodeRow>{});
public:
bool dumpToFile(const Path &filePath) const;
bool loadFromFile(const Path &filePath);
bool dumpToParquetFile(const Path &filePath) const;
bool loadFromParquetFile(const Path &filePath);
};
static_assert(serde::Serializable<InodeTable>);
std::vector<Path> listFilesFromPath(const Path path);
CoTryTask<Void> dumpInodesFromFdb(const std::string fdbClusterFile,
const uint32_t numInodesPerFile,
const std::string inodeDir,
const bool parquetFormat = false,
const bool dumpAllInodes = false,
const uint32_t threads = 4);
CoTryTask<robin_hood::unordered_set<meta::InodeId>> loadInodeFromFiles(
const Path inodePath,
const robin_hood::unordered_set<meta::InodeId> &inodeIdsToPeek,
const uint32_t parallel,
const bool parquetFormat = false,
std::time_t *inodeDumpTime = nullptr);
class Dispatcher;
CoTryTask<void> registerDumpInodesHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,89 @@
#include "client/cli/admin/DumpSession.h"
#include <fmt/core.h>
#include <folly/Likely.h>
#include <string>
#include "AdminEnv.h"
#include "analytics/SerdeObjectWriter.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/kv/ITransaction.h"
#include "common/kv/WithTransaction.h"
#include "common/utils/Result.h"
#include "fdb/FDBRetryStrategy.h"
#include "meta/components/SessionManager.h"
#include "meta/store/FileSession.h"
namespace hf3fs::client::cli {
namespace {
kv::FDBRetryStrategy getRetryStrategy() { return kv::FDBRetryStrategy({1_s, 10, false}); }
auto getParser() {
argparse::ArgumentParser parser("dump-session");
parser.add_argument("--output").help("output parquet filename");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleDumpSession(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
auto output = parser.present<std::string>("--output");
ENSURE_USAGE(!output->empty(), "must specify output filename");
ENSURE_USAGE(!boost::filesystem::exists(*output), "output filename already exists");
auto openRes = analytics::SerdeObjectWriter<meta::server::FileSession>::open(*output);
if (!openRes) {
XLOGF(ERR, "Failed to open writer {}", *output);
co_return makeError(StatusCode::kOSError, "failed to create writer");
}
auto &writer = *openRes;
size_t cnt = 0;
for (size_t shard = 0; shard < meta::server::FileSession::kShard; shard++) {
std::optional<meta::server::FileSession> prev;
while (true) {
auto handler = [&](kv::IReadOnlyTransaction &txn) -> CoTryTask<std::vector<meta::server::FileSession>> {
co_return co_await meta::server::FileSession::scan(txn, shard, prev);
};
auto sessions = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadWriteTransaction(), std::move(handler));
CO_RETURN_ON_ERROR(sessions);
if (sessions->empty()) {
break;
}
prev = sessions->back();
for (auto &s : *sessions) {
writer << s;
cnt++;
if (!writer) {
co_return makeError(StatusCode::kUnknown, "serde writer error");
}
}
if (cnt > 1 << 20) {
writer.endRowGroup();
if (!writer) {
co_return makeError(StatusCode::kUnknown, "serde writer error");
}
}
}
}
writer.endRowGroup();
if (!writer) {
co_return makeError(StatusCode::kUnknown, "serde writer error");
}
co_return Dispatcher::OutputTable{};
}
} // namespace
CoTryTask<void> registerDumpSessionHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleDumpSession);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerDumpSessionHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,287 @@
#include "FileWrapper.h"
#include "common/net/ib/RDMABuf.h"
#include "common/utils/Result.h"
namespace hf3fs::client::cli {
namespace {
auto rdmabufPool = net::RDMABufPool::create(256_MB, 1024);
} // namespace
static constexpr std::array<uint8_t, 16_MB> zeros{};
Result<std::vector<Block>> FileWrapper::prepareBlocks(size_t offset, size_t end, const flat::RoutingInfo &routingInfo) {
auto chunkSize = file().layout.chunkSize;
std::vector<Block> blocks;
while (offset < end) {
auto chainResult = file().getChainId(inode_, offset, routingInfo, 0);
RETURN_AND_LOG_ON_ERROR(chainResult);
auto chunk = file().getChunkId(inode_.id, offset).then([](auto chunk) { return storage::ChunkId(chunk); });
RETURN_AND_LOG_ON_ERROR(chunk);
auto next = std::min((offset / chunkSize + 1) * chunkSize, end);
blocks.push_back(Block{*chainResult, *chunk, offset % chunkSize, next - offset});
offset = next;
}
return blocks;
}
Result<uint32_t> FileWrapper::replicasNum() {
RETURN_AND_LOG_ON_ERROR(folly::coro::blockingWait(env_.mgmtdClientGetter()->refreshRoutingInfo(true)));
auto routingInfo = env_.mgmtdClientGetter()->getRoutingInfo();
if (routingInfo == nullptr || routingInfo->raw() == nullptr) {
return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto chainIdResult = file().getChainId(inode_, 0, *routingInfo->raw(), 0);
RETURN_AND_LOG_ON_ERROR(chainIdResult);
auto chainId = *chainIdResult;
auto chainResult = routingInfo->getChain(chainId);
if (!chainResult) {
return makeError(StorageClientCode::kRoutingError, fmt::format("chain info is null, {}", chainId));
}
uint32_t num = 0;
for (auto &info : chainResult->targets) {
num += info.publicState == flat::PublicTargetState::SERVING;
}
return num;
}
Result<Void> FileWrapper::showChunks(size_t offset, size_t length) {
RETURN_AND_LOG_ON_ERROR(folly::coro::blockingWait(env_.mgmtdClientGetter()->refreshRoutingInfo(true)));
auto routingInfo = env_.mgmtdClientGetter()->getRoutingInfo()->raw();
if (routingInfo == nullptr) {
return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto blocksResult = prepareBlocks(offset, offset + length, *routingInfo);
RETURN_AND_LOG_ON_ERROR(blocksResult);
auto &blocks = *blocksResult;
auto idx = 0;
for (auto &block : blocks) {
if (idx == 10) {
std::cout << "... (has more chunks)" << std::endl;
break;
}
std::cout << fmt::format("block{}: {}", idx++, block) << std::endl;
}
return Void{};
}
CoTryTask<storage::ChecksumInfo> FileWrapper::readFile(std::ostream &out,
size_t length,
size_t offset,
bool checksum,
bool hex,
storage::client::TargetSelectionMode mode,
MD5_CTX *md5 /* = nullptr */,
bool fillZero /* = false */,
bool verbose /* = false */,
uint32_t targetIndex /* = 0 */) {
auto routingInfo = env_.mgmtdClientGetter()->getRoutingInfo()->raw();
if (routingInfo == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto blocksResult = prepareBlocks(offset, offset + length, *routingInfo);
CO_RETURN_AND_LOG_ON_ERROR(blocksResult);
auto &blocks = *blocksResult;
co_return co_await readFile(env_, out, blocks, checksum, hex, mode, md5, fillZero, verbose, targetIndex);
}
CoTryTask<storage::ChecksumInfo> FileWrapper::readFile(AdminEnv &env,
std::ostream &out,
const std::vector<Block> &blocks,
bool checksum,
bool hex,
storage::client::TargetSelectionMode mode,
MD5_CTX *md5 /* = nullptr */,
bool fillZero /* = false */,
bool verbose /* = false */,
uint32_t targetIndex /* = 0 */) {
auto client = env.storageClientGetter();
auto buffer = co_await rdmabufPool->allocate();
if (UNLIKELY(!buffer)) {
XLOGF(ERR, "allocate buffer failed");
co_return makeError(RPCCode::kRDMANoBuf);
}
std::basic_string_view<uint8_t> data(buffer.ptr(), buffer.size());
auto readBuffer = storage::client::IOBuffer{buffer};
auto bufferOffset = 0;
std::vector<storage::client::ReadIO> batch;
storage::client::ReadOptions readOptions;
readOptions.set_enableChecksum(checksum);
readOptions.targetSelection().set_mode(mode);
readOptions.targetSelection().set_targetIndex(targetIndex);
storage::ChecksumInfo checksumInfo;
for (auto &block : blocks) {
auto readIO = client->createReadIO(block.chainId,
block.chunkId,
block.offset,
block.length,
(uint8_t *)&data[bufferOffset],
&readBuffer);
batch.push_back(std::move(readIO));
bufferOffset += block.length;
if (&block == &blocks.back() || bufferOffset + (&block + 1)->length > buffer.size()) {
auto result = co_await client->batchRead(batch, env.userInfo, readOptions);
CO_RETURN_AND_LOG_ON_ERROR(result);
for (auto &readIO : batch) {
auto succLength = 0ul;
if (readIO.result.lengthInfo) {
succLength = *readIO.result.lengthInfo;
} else if (readIO.result.lengthInfo.error().code() == StorageClientCode::kChunkNotFound && fillZero) {
} else {
CO_RETURN_AND_LOG_ON_ERROR(readIO.result.lengthInfo);
}
if (succLength != readIO.length) {
if (fillZero) {
if (verbose) {
std::cout << fmt::format("{} find hole, expected size {}, actual size {}\n",
readIO.chunkId,
readIO.length,
succLength);
}
auto needFill = readIO.length - succLength;
CO_RETURN_AND_LOG_ON_ERROR(readIO.result.checksum.combine(
storage::ChecksumInfo::create(storage::ChecksumType::CRC32C, zeros.data(), needFill),
needFill));
succLength = readIO.length;
} else {
co_return makeError(StatusCode::kInvalidFormat,
fmt::format("read is short, chain {} chunk {} offset {} expect {} read {}",
readIO.routingTarget.chainId,
readIO.chunkId,
readIO.offset,
readIO.length,
succLength));
}
}
CO_RETURN_AND_LOG_ON_ERROR(checksumInfo.combine(readIO.result.checksum, succLength));
}
// clean up.
if (hex) {
out << fmt::format("{:02X}", fmt::join(data.substr(0, bufferOffset), " "));
} else {
out.write(reinterpret_cast<const char *>(data.data()), bufferOffset);
}
if (md5) {
int ret = MD5_Update(md5, reinterpret_cast<const char *>(data.data()), bufferOffset);
if (UNLIKELY(ret != 1)) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("md5 update error {}", ret));
}
}
if (!out) {
co_return makeError(StatusCode::kInvalidArg, "write to stream failed");
}
batch.clear();
bufferOffset = 0;
}
}
if (hex) {
out << std::endl;
}
co_return checksumInfo;
}
CoTryTask<storage::ChecksumInfo> FileWrapper::writeFile(std::istream &in,
size_t length,
size_t offset,
Duration timeout,
bool syncAfterWrite /* = true */) {
auto chunkSize = file().layout.chunkSize;
auto client = env_.storageClientGetter();
std::vector<uint8_t> data(64_MB, 0);
auto registerResult = client->registerIOBuffer(&data[0], data.size());
CO_RETURN_AND_LOG_ON_ERROR(registerResult);
auto writeBuffer = std::move(*registerResult);
auto bufferOffset = 0;
std::vector<storage::client::WriteIO> batch;
storage::client::WriteOptions writeOptions;
writeOptions.retry().set_init_wait_time(timeout);
writeOptions.retry().set_max_wait_time(timeout);
writeOptions.retry().set_max_retry_time(timeout * 10);
auto routingInfo = env_.mgmtdClientGetter()->getRoutingInfo()->raw();
if (routingInfo == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto blocksResult = prepareBlocks(offset, offset + length, *routingInfo);
CO_RETURN_AND_LOG_ON_ERROR(blocksResult);
auto &blocks = *blocksResult;
storage::ChecksumInfo checksum;
for (auto &block : blocks) {
if (!in.read(reinterpret_cast<char *>(&data[bufferOffset]), block.length)) {
co_return makeError(StatusCode::kInvalidArg, "write from file failed");
}
auto writeIO = client->createWriteIO(block.chainId,
block.chunkId,
block.offset,
block.length,
chunkSize,
&data[bufferOffset],
&writeBuffer);
batch.push_back(std::move(writeIO));
bufferOffset += block.length;
if (bufferOffset + chunkSize > data.capacity() || &block == &blocks.back()) {
auto result = co_await client->batchWrite(batch, env_.userInfo, writeOptions);
CO_RETURN_AND_LOG_ON_ERROR(result);
for (auto &writeIO : batch) {
CO_RETURN_AND_LOG_ON_ERROR(writeIO.result.lengthInfo);
auto succLength = *writeIO.result.lengthInfo;
if (succLength != writeIO.length) {
co_return makeError(StatusCode::kInvalidFormat,
fmt::format("write is short, chain {} chunk {} offset {} expect {} write {}",
writeIO.routingTarget.chainId,
writeIO.chunkId,
writeIO.offset,
writeIO.length,
succLength));
}
CO_RETURN_AND_LOG_ON_ERROR(checksum.combine(writeIO.result.checksum, succLength));
}
// clean up.
batch.clear();
bufferOffset = 0;
}
}
if (syncAfterWrite) {
auto syncResult = co_await sync();
CO_RETURN_AND_LOG_ON_ERROR(syncResult);
}
co_return checksum;
}
CoTryTask<FileWrapper> FileWrapper::openOrCreateFile(AdminEnv &env,
const Path &src,
bool createIsMissing /* = false */) {
meta::Inode inode;
auto statResult = co_await env.metaClientGetter()->stat(env.userInfo, env.currentDirId, src, true);
if (!statResult && statResult.error().code() == MetaCode::kNotFound && createIsMissing) {
auto result = co_await env.metaClientGetter()
->create(env.userInfo, env.currentDirId, src, std::nullopt, meta::Permission(0644), 0);
CO_RETURN_AND_LOG_ON_ERROR(result);
inode = *result;
} else if (!statResult) {
CO_RETURN_AND_LOG_ON_ERROR(statResult);
} else {
inode = *statResult;
}
if (!inode.isFile()) {
co_return makeError(StatusCode::kInvalidArg, "not a file");
}
FileWrapper file(env);
file.inode_ = std::move(inode);
co_return file;
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,83 @@
#pragma once
#include <folly/experimental/coro/BlockingWait.h>
#include <fstream>
#define OPENSSL_SUPPRESS_DEPRECATED
#include <openssl/md5.h>
#include <optional>
#include <sstream>
#include "client/cli/admin/AdminEnv.h"
#include "client/cli/admin/Layout.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "client/storage/TargetSelection.h"
#include "common/serde/Serde.h"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
struct Block {
SERDE_STRUCT_FIELD(chainId, storage::ChainId{});
SERDE_STRUCT_FIELD(chunkId, storage::ChunkId{});
SERDE_STRUCT_FIELD(offset, uint64_t{});
SERDE_STRUCT_FIELD(length, uint64_t{});
};
class FileWrapper {
public:
FileWrapper(AdminEnv &env)
: env_(env) {}
auto &file() { return inode_.asFile(); }
auto &file() const { return inode_.asFile(); }
auto length() const { return file().length; }
auto truncate(size_t end) { return env_.metaClientGetter()->truncate(env_.userInfo, inode_.id, end); }
auto sync() { return env_.metaClientGetter()->sync(env_.userInfo, inode_.id, false, true, std::nullopt); }
Result<std::vector<Block>> prepareBlocks(size_t offset, size_t end, const flat::RoutingInfo &routingInfo);
Result<uint32_t> replicasNum();
Result<Void> showChunks(size_t offset, size_t length);
CoTryTask<storage::ChecksumInfo> readFile(
std::ostream &out,
size_t length,
size_t offset,
bool checksum = true,
bool hex = false,
storage::client::TargetSelectionMode mode = storage::client::TargetSelectionMode::Default,
MD5_CTX *md5 = nullptr,
bool fillZero = false,
bool verbose = false,
uint32_t targetIndex = 0);
static CoTryTask<storage::ChecksumInfo> readFile(
AdminEnv &env,
std::ostream &out,
const std::vector<Block> &blocks,
bool checksum = true,
bool hex = false,
storage::client::TargetSelectionMode mode = storage::client::TargetSelectionMode::Default,
MD5_CTX *md5 = nullptr,
bool fillZero = false,
bool verbose = false,
uint32_t targetIndex = 0);
CoTryTask<storage::ChecksumInfo> writeFile(std::istream &in,
size_t length,
size_t offset,
Duration timeout,
bool syncAfterWrite = true);
static CoTryTask<FileWrapper> openOrCreateFile(AdminEnv &env, const Path &src, bool createIsMissing = false);
private:
AdminEnv &env_;
SERDE_CLASS_FIELD(inode, meta::Inode{});
};
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,123 @@
#include <folly/experimental/coro/BlockingWait.h>
#include <fstream>
#include <sstream>
#include "AdminEnv.h"
#include "WriteFile.h"
#include "client/cli/admin/FileWrapper.h"
#include "client/cli/admin/Layout.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "common/serde/Serde.h"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("fill-zero");
parser.add_argument("path");
parser.add_argument("--dry-run").default_value(false).implicit_value(true);
parser.add_argument("--verbose").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleFillZero(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
// 1. parse arguments.
Path src = parser.get<std::string>("path");
auto dryRun = parser.get<bool>("--dry-run");
auto verbose = parser.get<bool>("--verbose");
auto rdmabufPool = net::RDMABufPool::create(32_MB, 1024);
if (dryRun) {
std::cout << "Dry-run mode, no data will be written" << std::endl;
}
// 2. stat file.
auto openResult = co_await FileWrapper::openOrCreateFile(env, src, true);
CO_RETURN_AND_LOG_ON_ERROR(openResult);
auto &file = *openResult;
auto storageClient = env.storageClientGetter();
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo()->raw();
if (UNLIKELY(routingInfo == nullptr)) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
// 3. read and fill.
storage::client::ReadOptions readOptions;
storage::client::WriteOptions writeOptions;
uint64_t chunkSize = file.file().layout.chunkSize;
for (auto startPoint = 0ul; startPoint < file.length(); startPoint += 32_MB) {
std::vector<storage::client::ReadIO> readIOs;
std::vector<storage::client::WriteIO> writeIOs;
net::RDMABuf current = co_await rdmabufPool->allocate();
storage::client::IOBuffer buffer(current);
for (auto offset = startPoint; offset < file.length() && offset < startPoint + 32_MB; offset += chunkSize) {
auto chainResult = file.file().getChainId(file.inode(), offset, *routingInfo, 0);
CO_RETURN_AND_LOG_ON_ERROR(chainResult);
auto chunk =
file.file().getChunkId(file.inode().id, offset).then([](auto chunk) { return storage::ChunkId(chunk); });
CO_RETURN_AND_LOG_ON_ERROR(chunk);
auto l = std::min(file.file().length - offset, chunkSize);
readIOs.push_back(
storageClient->createReadIO(*chainResult, *chunk, offset % chunkSize, l, current.ptr(), &buffer));
current.advance(chunkSize);
}
auto readResult = co_await storageClient->batchRead(readIOs, env.userInfo, readOptions);
CO_RETURN_AND_LOG_ON_ERROR(readResult);
net::RDMABuf writeCurrent = co_await rdmabufPool->allocate();
storage::client::IOBuffer writeBuffer(writeCurrent);
std::memset(writeCurrent.ptr(), 0, 32_MB);
for (auto &readIO : readIOs) {
auto succLength = 0ul;
if (readIO.result.lengthInfo) {
succLength = *readIO.result.lengthInfo;
} else if (readIO.result.lengthInfo.error().code() != StorageClientCode::kChunkNotFound) {
CO_RETURN_AND_LOG_ON_ERROR(readIO.result.lengthInfo);
}
if (succLength < readIO.length) {
if (verbose || dryRun) {
std::cout << fmt::format("{} find hole, expected size {}, actual size {}\n",
readIO.chunkId,
readIO.length,
succLength);
}
writeIOs.push_back(storageClient->createWriteIO(readIO.routingTarget.chainId,
readIO.chunkId,
succLength,
readIO.length - succLength,
chunkSize,
writeCurrent.ptr(),
&writeBuffer));
writeCurrent.advance(chunkSize);
}
}
if (!writeIOs.empty() && !dryRun) {
auto writeResult = co_await storageClient->batchWrite(writeIOs, env.userInfo, writeOptions);
CO_RETURN_AND_LOG_ON_ERROR(writeResult);
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerFillZeroHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleFillZero);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerFillZeroHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,183 @@
#include "FindOrphanedChunks.h"
#include <folly/logging/xlog.h>
#include <vector>
#include "AdminEnv.h"
#include "DumpChunkMeta.h"
#include "DumpInodes.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/IEnv.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("find-orphaned-chunks");
parser.add_argument("-i", "--inode-path").default_value(std::string{"inodes"});
parser.add_argument("-m", "--chunkmeta-path").default_value(std::string{"chunkmeta"});
parser.add_argument("-n", "--inode-ids").nargs(argparse::nargs_pattern::any).scan<'x', uint64_t>();
parser.add_argument("-o", "--orphaned-dir").default_value(std::string{"orphaned"});
parser.add_argument("-x", "--ignore-chunkid-prefix").default_value(std::string{"F"});
parser.add_argument("-v", "--only-chunkid-prefix").default_value(std::string{""});
parser.add_argument("-S", "--skip-safety-check").default_value(false).implicit_value(true);
parser.add_argument("-q", "--parquet-format").default_value(false).implicit_value(true);
parser.add_argument("-p", "--parallel").default_value(uint32_t{32}).scan<'u', uint32_t>();
return parser;
}
CoTryTask<Dispatcher::OutputTable> findOrphanedChunks(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
ENSURE_USAGE(env.mgmtdClientGetter);
Dispatcher::OutputTable table;
const auto &inodePath = parser.get<std::string>("inode-path");
const auto &chunkmetaPath = parser.get<std::string>("chunkmeta-path");
const auto &inodeInt64Ids = parser.get<std::vector<uint64_t>>("inode-ids");
const auto &orphanedDir = parser.get<std::string>("orphaned-dir");
const auto &ignoreChunkIdPrefix = parser.get<std::string>("ignore-chunkid-prefix");
const auto &onlyChunkIdPrefix = parser.get<std::string>("only-chunkid-prefix");
const auto &parquetFormat = parser.get<bool>("parquet-format");
const auto &skipSafetyCheck = parser.get<bool>("skip-safety-check");
const auto parallel = std::min(parser.get<uint32_t>("parallel"), std::thread::hardware_concurrency() / 2);
if (boost::filesystem::exists(orphanedDir)) {
XLOGF(CRITICAL, "Output directory for orphaned chunks already exists: {}", orphanedDir);
co_return makeError(StatusCode::kInvalidArg);
}
boost::system::error_code err{};
boost::filesystem::create_directories(orphanedDir, err);
if (UNLIKELY(err.failed())) {
XLOGF(CRITICAL, "Failed to create directory {}, error: {}", orphanedDir, err.message());
co_return makeError(StatusCode::kIOError);
}
std::time_t inodeDumpTime(std::time(nullptr));
robin_hood::unordered_set<meta::InodeId> inodeIdsToPeek;
for (const auto &n : inodeInt64Ids) inodeIdsToPeek.insert(meta::InodeId{n});
auto uniqInodeIds = co_await loadInodeFromFiles(inodePath, inodeIdsToPeek, parallel, parquetFormat, &inodeDumpTime);
if (!uniqInodeIds) {
XLOGF(FATAL, "Failed to load inodes from directory {}, error: {}", inodePath, uniqInodeIds.error());
co_return makeError(uniqInodeIds.error());
}
if (!inodeIdsToPeek.empty()) {
XLOGF(CRITICAL, "Completed to find {} inodes in path: {}", inodeIdsToPeek.size(), inodePath);
co_return table;
}
std::vector<Path> chunkmetaFilePaths = listFilesFromPath(chunkmetaPath);
XLOGF(CRITICAL, "Processing {} chunk metadata files in path: {}", chunkmetaFilePaths.size(), chunkmetaPath);
std::atomic_size_t numOrphanedChunks = 0;
std::atomic_size_t numIgnoredOrphanedChunks = 0;
std::atomic_size_t numProcessedChunks = 0;
std::atomic_size_t sizeOfOrphanedChunks = 0;
std::atomic_size_t sizeOfIgnoredOrphanedChunks = 0;
auto executor = std::make_unique<folly::CPUThreadPoolExecutor>(parallel);
CountDownLatch<folly::fibers::Baton> loadChunkmetaDone(chunkmetaFilePaths.size());
for (size_t chunkmetaFileIndex = 0; chunkmetaFileIndex < chunkmetaFilePaths.size(); chunkmetaFileIndex++) {
executor->add([&, chunkmetaFileIndex, chunkmetaFilePath = chunkmetaFilePaths[chunkmetaFileIndex]]() {
auto guard = folly::makeGuard([&loadChunkmetaDone]() { loadChunkmetaDone.countDown(); });
ChunkMetaTable metadata;
bool ok =
parquetFormat ? metadata.loadFromParquetFile(chunkmetaFilePath) : metadata.loadFromFile(chunkmetaFilePath);
if (!ok) {
XLOGF(FATAL, "Failed to load file: {}", chunkmetaFilePath);
return false;
}
if (!skipSafetyCheck && metadata.timestamp >= inodeDumpTime) {
XLOGF(FATAL,
"Chunk metadata dump time '{:%c}' >= inode snapshot time '{:%c}', skipping file: {}",
fmt::localtime(metadata.timestamp),
fmt::localtime(inodeDumpTime),
chunkmetaFilePath);
return false;
}
ChunkMetaTable orphanedMetadata{.chainId = metadata.chainId,
.targetId = metadata.targetId,
.timestamp = metadata.timestamp};
for (const auto &chunk : metadata.chunks) {
const auto &chunkmeta = chunk.chunkmeta;
if (chunkmeta.chunkId != storage::ChunkId{} && !uniqInodeIds->count(chunk.inodeId)) {
if ((!ignoreChunkIdPrefix.empty() && chunkmeta.chunkId.toString().starts_with(ignoreChunkIdPrefix)) ||
(!onlyChunkIdPrefix.empty() && !chunkmeta.chunkId.toString().starts_with(onlyChunkIdPrefix))) {
XLOGF(DBG, "Ignore an orphaned chunk on {}@{}: {}", chunkmeta, metadata.targetId, metadata.chainId);
numIgnoredOrphanedChunks++;
sizeOfIgnoredOrphanedChunks += chunkmeta.length;
} else {
XLOGF(DBG, "Found an orphaned chunk on {}@{}: {}", chunkmeta, metadata.targetId, metadata.chainId);
numOrphanedChunks++;
sizeOfOrphanedChunks += chunkmeta.length;
orphanedMetadata.chunks.push_back(chunk);
}
}
}
if (!orphanedMetadata.chunks.empty()) {
auto orphanedChunkFilePath = Path(orphanedDir) / chunkmetaFilePath.filename();
bool writeOk = parquetFormat
? orphanedMetadata.dumpToParquetFile(orphanedChunkFilePath.replace_extension(".parquet"))
: orphanedMetadata.dumpToFile(orphanedChunkFilePath, true /*jsonFormat*/);
if (writeOk) {
XLOGF(CRITICAL,
"{} orphaned chunks on {}@{} saved to file: {}",
orphanedMetadata.chunks.size(),
orphanedMetadata.targetId,
orphanedMetadata.chainId,
orphanedChunkFilePath);
}
}
XLOGF(WARN,
"#{}/{} Processed metadata of {} chunks in file: {}",
chunkmetaFileIndex + 1,
chunkmetaFilePaths.size(),
metadata.chunks.size(),
chunkmetaFilePath);
numProcessedChunks += metadata.chunks.size();
return true;
});
}
co_await loadChunkmetaDone.wait();
executor->join();
XLOGF(CRITICAL,
"In total {}/{} orphaned chunks ({:.3f} GB) ignored",
numIgnoredOrphanedChunks.load(),
numProcessedChunks.load(),
double(sizeOfIgnoredOrphanedChunks.load()) / 1_GB);
XLOGF(CRITICAL,
"In total {}/{} orphaned chunks ({:.3f} GB) saved to directory: {}",
numOrphanedChunks.load(),
numProcessedChunks.load(),
double(sizeOfOrphanedChunks.load()) / 1_GB,
orphanedDir);
co_return table;
}
} // namespace
CoTryTask<void> registerFindOrphanedChunksHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, findOrphanedChunks);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
#include "common/utils/Path.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerFindOrphanedChunksHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,180 @@
#include "GetConfig.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/FileUtils.h"
#include "common/utils/StringUtils.h"
namespace hf3fs::client::cli {
namespace {
const std::map<String, flat::NodeType> typeMappings = {
{"MGMTD", flat::NodeType::MGMTD},
{"META", flat::NodeType::META},
{"STORAGE", flat::NodeType::STORAGE},
{"CLIENT", flat::NodeType::CLIENT},
{"CLIENT_AGENT", flat::NodeType::CLIENT},
{"FUSE", flat::NodeType::FUSE},
};
const std::set<String> &typeChoices() {
static auto choices = [] {
std::set<String> s;
for (const auto &[k, _] : typeMappings) {
s.insert(k);
}
return s;
}();
return choices;
}
auto getParser() {
argparse::ArgumentParser parser("get-config");
parser.add_argument("-n", "--node-id").scan<'u', uint32_t>();
parser.add_argument("-c", "--client-id");
parser.add_argument("-a", "--addr");
parser.add_argument("-t", "--node-type").help(fmt::format("choices : {}", fmt::join(typeChoices(), " | ")));
parser.add_argument("-l", "--list-versions")
.help("list versions of all types")
.default_value(false)
.implicit_value(true);
parser.add_argument("-k", "--config-key");
parser.add_argument("-o", "--output-file").help("the path where config is saved at");
return parser;
}
CoTryTask<void> handleConfigOfType(Dispatcher::OutputTable &table,
IMgmtdClientForAdmin &client,
flat::NodeType t,
std::optional<String> outputFile) {
auto configRes = co_await client.getConfig(t, flat::ConfigVersion(0));
CO_RETURN_ON_ERROR(configRes);
const auto &optionalConfig = *configRes;
table.push_back({"NodeType", toString(t)});
if (optionalConfig) {
table.push_back({"ConfigVersion", std::to_string(optionalConfig->configVersion)});
table.push_back({"ConfigDesc", optionalConfig->desc});
if (outputFile) {
CO_RETURN_ON_ERROR(storeToFile(*outputFile, optionalConfig->content));
table.push_back({"OutputFile", *outputFile});
}
} else {
table.push_back({"ConfigVersion", "null"});
}
co_return Void{};
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto nodeId = parser.present<uint32_t>("-n");
auto clientId = parser.present<String>("-c");
auto addr = parser.present<String>("-a");
auto type = parser.present<String>("-t");
auto listVersions = parser.get<bool>("-l");
auto configKey = parser.present<String>("-k").value_or("");
auto outputFile = parser.present<String>("-o");
ENSURE_USAGE(nodeId.has_value() + clientId.has_value() + type.has_value() + addr.has_value() + listVersions == 1,
"must and can only specify one of -n, -c, -t, -a, and -l");
if (nodeId) {
ENSURE_USAGE(outputFile.has_value(), "must specify -o with -n");
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto node = routingInfo->getNode(flat::NodeId(*nodeId));
if (!node) {
co_return makeError(MgmtdCode::kNodeNotFound);
}
auto addresses = node->extractAddresses("Core");
auto res = co_await env.coreClientGetter()->getConfig(addresses, core::GetConfigReq::create(configKey));
CO_RETURN_ON_ERROR(res);
table.push_back({"NodeId", std::to_string(*nodeId)});
CO_RETURN_ON_ERROR(storeToFile(*outputFile, res->config));
table.push_back({"OutputFile", *outputFile});
} else if (type) {
ENSURE_USAGE(typeMappings.contains(*type), fmt::format("invalid type: {}", *type));
ENSURE_USAGE(outputFile.has_value(), "must specify -o with -t");
CO_RETURN_ON_ERROR(
co_await handleConfigOfType(table, *env.mgmtdClientGetter(), typeMappings.at(*type), outputFile));
} else if (clientId) {
ENSURE_USAGE(outputFile.has_value(), "must specify -o with -c");
auto clientSession = co_await env.mgmtdClientGetter()->getClientSession(*clientId);
CO_RETURN_ON_ERROR(clientSession);
if (clientSession->session) {
auto addresses = flat::extractAddresses(clientSession->session->serviceGroups, "Core");
auto res = co_await env.coreClientGetter()->getConfig(addresses, core::GetConfigReq::create(configKey));
CO_RETURN_ON_ERROR(res);
table.push_back({"ClientId", *clientId});
CO_RETURN_ON_ERROR(storeToFile(*outputFile, res->config));
table.push_back({"OutputFile", *outputFile});
} else {
table.push_back({"Session not found"});
table.push_back({"ClientId", *clientId});
table.push_back({"MgmtdBootstrapping", clientSession->bootstrapping ? "Yes" : "No"});
}
} else if (addr) {
ENSURE_USAGE(outputFile.has_value(), "must specify -o with -a");
auto addrRes = net::Address::from(*addr);
CO_RETURN_ON_ERROR(addrRes);
auto res = co_await env.coreClientGetter()->getConfig(std::vector{*addrRes}, core::GetConfigReq::create(configKey));
CO_RETURN_ON_ERROR(res);
table.push_back({"Address", *addr});
CO_RETURN_ON_ERROR(storeToFile(*outputFile, res->config));
table.push_back({"OutputFile", *outputFile});
} else {
ENSURE_USAGE(listVersions);
table.push_back({"Type", "ConfigVersion"});
RHStringHashMap<flat::ConfigVersion> versions;
auto res = co_await env.mgmtdClientGetter()->getConfigVersions();
if (res) {
versions = std::move(*res);
} else {
auto values = magic_enum::enum_values<flat::NodeType>();
for (auto t : values) {
auto configRes = co_await env.mgmtdClientGetter()->getConfig(t, flat::ConfigVersion(0));
CO_RETURN_ON_ERROR(configRes);
const auto &optionalConfig = *configRes;
if (optionalConfig) {
versions[toString(t)] = optionalConfig->configVersion;
} else {
versions[toString(t)] = flat::ConfigVersion(0);
}
}
}
for (const auto &[k, v] : versions) {
table.push_back({k, v == 0 ? "N/A" : std::to_string(v)});
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerGetConfigHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerGetConfigHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,83 @@
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "RenderConfig.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/FileUtils.h"
#include "common/utils/StringUtils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("get-last-config-update-record");
parser.add_argument("-n", "--node-id").scan<'u', uint32_t>();
parser.add_argument("-c", "--client-id");
parser.add_argument("-a", "--addr");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto nodeId = parser.present<uint32_t>("-n");
auto clientId = parser.present<String>("-c");
auto addr = parser.present<String>("-a");
ENSURE_USAGE(nodeId.has_value() + clientId.has_value() + addr.has_value() == 1,
"must and can only specify one of -n, -c, and -a");
std::vector<net::Address> addresses;
if (nodeId) {
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto node = routingInfo->getNode(flat::NodeId(*nodeId));
if (!node) {
co_return makeError(MgmtdCode::kNodeNotFound);
}
addresses = node->extractAddresses("Core");
table.push_back({"NodeId", std::to_string(*nodeId)});
} else if (clientId) {
auto clientSession = co_await env.mgmtdClientGetter()->getClientSession(*clientId);
CO_RETURN_ON_ERROR(clientSession);
if (clientSession->session) {
addresses = flat::extractAddresses(clientSession->session->serviceGroups, "Core");
}
table.push_back({"ClientId", *clientId});
} else if (addr) {
auto res = net::Address::from(*addr);
CO_RETURN_ON_ERROR(res);
addresses.push_back(*res);
table.push_back({"Address", *addr});
}
auto req = core::GetLastConfigUpdateRecordReq::create();
auto res = co_await env.coreClientGetter()->getLastConfigUpdateRecord(addresses, req);
CO_RETURN_ON_ERROR(res);
if (res->record.has_value()) {
const auto &record = res->record.value();
table.push_back({"UpdateTime", record.updateTime.YmdHMS()});
table.push_back({"Description", record.description});
table.push_back({"Result", record.result.describe()});
} else {
table.push_back({"UpdateTime", "N/A"});
table.push_back({"Description", "N/A"});
table.push_back({"Result", "N/A"});
}
co_return table;
}
} // namespace
CoTryTask<void> registerGetLastConfigUpdateRecordHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerGetLastConfigUpdateRecordHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,35 @@
#include "GetRealPath.h"
#include <fmt/chrono.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/MagicEnum.hpp"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("getrealpath");
parser.add_argument("path");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleGetRealPath(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto path = parser.present<std::string>("path").value_or(env.currentDir);
auto res = co_await env.metaClientGetter()->getRealPath(env.userInfo, env.currentDirId, Path(path), true);
CO_RETURN_ON_ERROR(res);
table.push_back({res.value().native()});
co_return table;
}
} // namespace
CoTryTask<void> registerGetRealPathHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleGetRealPath);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerGetRealPathHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,121 @@
#include "HotUpdateConfig.h"
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/FileUtils.h"
#include "common/utils/MagicEnum.hpp"
#include "common/utils/Result.h"
#include "common/utils/StringUtils.h"
#include "fbs/core/service/Rpc.h"
#include "fbs/mgmtd/MgmtdTypes.h"
namespace hf3fs::client::cli {
namespace {
const std::set<String> typeChoices(magic_enum::enum_names<flat::NodeType>().begin(),
magic_enum::enum_names<flat::NodeType>().end());
auto getParser() {
argparse::ArgumentParser parser("hot-update-config");
parser.add_argument("-n", "--node-id").scan<'u', uint32_t>();
parser.add_argument("-t", "--node-type")
.help(fmt::format("choices : {}", fmt::join(typeChoices.begin(), typeChoices.end(), " | ")));
parser.add_argument("-c", "--client-id");
parser.add_argument("-a", "--addr");
parser.add_argument("-s", "--string");
parser.add_argument("-f", "--file");
parser.add_argument("--render").default_value(false);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto nodeId = parser.present<uint32_t>("-n");
auto type = parser.present<String>("-t");
auto clientId = parser.present<String>("-c");
auto addr = parser.present<String>("-a");
auto str = parser.present<String>("-s");
auto filePath = parser.present<String>("-f");
auto render = parser.get<bool>("--render");
ENSURE_USAGE(nodeId.has_value() + clientId.has_value() + addr.has_value() + type.has_value() == 1,
"must and can only specify one of -n, -t, -c, and -a");
ENSURE_USAGE(str.has_value() + filePath.has_value() == 1, "must and can only specify one of -s and -f");
auto updateString = [&]() -> Result<String> {
if (str.has_value()) return *str;
return loadFile(*filePath);
}();
CO_RETURN_ON_ERROR(updateString);
try {
[[maybe_unused]] auto result = toml::parse(*updateString);
} catch (const toml::parse_error &e) {
std::stringstream ss;
ss << e;
XLOGF(ERR, "Parse config failed: {}", ss.str());
co_return makeError(StatusCode::kConfigParseError, fmt::format("Invalid toml: {}", e.what()));
} catch (std::exception &e) {
co_return makeError(StatusCode::kConfigInvalidValue, fmt::format("Invalid toml: {}", e.what()));
}
auto doUpdate = [&](auto addresses) -> CoTryTask<core::HotUpdateConfigRsp> {
auto req = core::HotUpdateConfigReq::create(*updateString, render);
co_return co_await env.coreClientGetter()->hotUpdateConfig(addresses, req);
};
if (type) {
auto ntype = magic_enum::enum_cast<flat::NodeType>(*type);
ENSURE_USAGE(ntype, fmt::format("invalid type: {}", *type));
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto nodes = routingInfo->getNodeBy(flat::selectNodeByType(*ntype) && flat::selectActiveNode());
for (const auto &node : nodes) {
auto addrs = node.extractAddresses("Core");
auto result = co_await doUpdate(addrs);
table.push_back(
{"NodeId", std::to_string(node.app.nodeId), result.hasError() ? result.error().describe() : "success"});
}
co_return table;
}
std::vector<net::Address> addresses;
if (nodeId) {
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto node = routingInfo->getNode(flat::NodeId(*nodeId));
if (!node) {
co_return makeError(MgmtdCode::kNodeNotFound);
}
addresses = node->extractAddresses("Core");
table.push_back({"NodeId", std::to_string(*nodeId)});
} else if (clientId) {
auto clientSession = co_await env.mgmtdClientGetter()->getClientSession(*clientId);
CO_RETURN_ON_ERROR(clientSession);
if (clientSession->session) {
addresses = flat::extractAddresses(clientSession->session->serviceGroups, "Core");
}
table.push_back({"ClientId", *clientId});
} else if (addr) {
auto res = net::Address::from(*addr);
CO_RETURN_ON_ERROR(res);
addresses.push_back(*res);
}
CO_RETURN_ON_ERROR(co_await doUpdate(addresses));
table.push_back({"Succeed"});
co_return table;
}
} // namespace
CoTryTask<void> registerHotUpdateConfigHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerHotUpdateConfigHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,182 @@
#include "InitCluster.h"
#include <fmt/core.h>
#include <folly/Conv.h>
#include <folly/logging/xlog.h>
#include <memory>
#include "AdminEnv.h"
#include "Utils.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "common/kv/WithTransaction.h"
#include "common/utils/FileUtils.h"
#include "common/utils/Result.h"
#include "common/utils/StringUtils.h"
#include "fbs/mgmtd/ChainRef.h"
#include "fdb/FDBRetryStrategy.h"
#include "fuse/FuseConfig.h"
#include "meta/components/ChainAllocator.h"
#include "meta/service/MetaServer.h"
#include "meta/store/MetaStore.h"
#include "mgmtd/service/MgmtdConfig.h"
#include "mgmtd/store/MgmtdStore.h"
#include "storage/service/StorageServer.h"
namespace hf3fs::client::cli {
namespace {
using namespace std::chrono_literals;
kv::FDBRetryStrategy getRetryStrategy() { return kv::FDBRetryStrategy({1_s, 10, false}); }
auto getParser() {
argparse::ArgumentParser parser("init-cluster");
parser.add_argument("chaintableid").scan<'u', uint32_t>();
parser.add_argument("chunksize").scan<'u', uint32_t>();
parser.add_argument("stripesize").scan<'u', uint32_t>();
parser.add_argument("--mgmtd", "--mgmtd-config-path");
parser.add_argument("--meta", "--meta-config-path");
parser.add_argument("--storage", "--storage-config-path");
parser.add_argument("--fuse", "--fuse-config-path");
parser.add_argument("--allow-config-existed").default_value(false).implicit_value(true);
parser.add_argument("--skip-config-check").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<void> handleInitFileSystem(AdminEnv &env,
const argparse::ArgumentParser &parser,
Dispatcher::OutputTable &table) {
auto tableId = flat::ChainTableId(parser.get<uint32_t>("chaintableid"));
auto chunksize = parser.get<uint32_t>("chunksize");
auto stripesize = parser.get<uint32_t>("stripesize");
auto chainAlloc = std::make_unique<meta::server::ChainAllocator>(nullptr);
auto rootLayout = meta::Layout::newEmpty(tableId, chunksize, stripesize);
auto op = meta::server::MetaStore::initFileSystem(*chainAlloc, rootLayout);
auto handler = [&](kv::IReadWriteTransaction &txn) -> CoTryTask<void> { co_return co_await op->run(txn); };
auto commitRes = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadWriteTransaction(), std::move(handler));
if (commitRes.hasError()) {
co_return makeError(commitRes.error().code(), fmt::format("Failed to init filesystem: {}", commitRes.error()));
};
table.push_back({fmt::format("Init filesystem, root directory layout: chain table {}, chunksize {}, stripesize {}\n",
tableId,
chunksize,
stripesize)});
co_return Void{};
}
CoTryTask<void> handleInitConfig(AdminEnv &env,
Dispatcher::OutputTable &table,
flat::NodeType nodeType,
config::IConfig &&cfg [[maybe_unused]],
mgmtd::MgmtdStore &store,
const String &filePath,
bool allowConfigExisted,
bool skipConfigCheck) {
auto configStr = loadFile(filePath);
CO_RETURN_ON_ERROR(configStr);
if (!skipConfigCheck) {
auto validateRes = cfg.update(std::string_view(*configStr), /*isHotUpdate=*/false);
if (validateRes.hasError()) {
co_return makeError(validateRes.error().code(),
fmt::format("Validate config for {} failed: {}", toString(nodeType), validateRes.error()));
}
}
auto handler = [&](kv::IReadWriteTransaction &txn) -> CoTryTask<flat::ConfigVersion> {
auto configInfo = co_await store.loadLatestConfig(txn, nodeType);
CO_RETURN_ON_ERROR(configInfo);
flat::ConfigVersion ver(1);
if (configInfo->has_value()) {
if (!allowConfigExisted) {
co_return makeError(StatusCode::kUnknown,
fmt::format("Config for {} existed, version {}",
toString(nodeType),
configInfo->value().configVersion.toUnderType()));
}
ver = flat::ConfigVersion(configInfo->value().configVersion + 1);
}
auto writeRes = co_await store.storeConfig(txn, nodeType, flat::ConfigInfo::create(ver, std::move(*configStr)));
CO_RETURN_ON_ERROR(writeRes);
co_return ver;
};
auto commitRes = co_await kv::WithTransaction(getRetryStrategy())
.run(env.kvEngineGetter()->createReadWriteTransaction(), std::move(handler));
if (commitRes.hasError()) {
co_return makeError(commitRes.error().code(),
fmt::format("Failed to set config for {}: {}", toString(nodeType), commitRes.error()));
};
table.push_back({fmt::format("Init config for {} version {}", toString(nodeType), commitRes->toUnderType())});
co_return Void{};
}
CoTryTask<Dispatcher::OutputTable> handleInitCluster(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
CO_RETURN_ON_ERROR(co_await handleInitFileSystem(env, parser, table));
auto allowConfigExisted = parser.get<bool>("--allow-config-existed");
// TODO: consider how to check
// auto skipConfigCheck = parser.get<bool>("skip-config-check");
auto skipConfigCheck = true;
mgmtd::MgmtdStore store;
if (auto mgmtdConfigPath = parser.present<String>("--mgmtd"); mgmtdConfigPath) {
CO_RETURN_ON_ERROR(co_await handleInitConfig(env,
table,
flat::NodeType::MGMTD,
mgmtd::MgmtdConfig{},
store,
*mgmtdConfigPath,
allowConfigExisted,
skipConfigCheck));
}
if (auto configPath = parser.present<String>("--meta"); configPath) {
CO_RETURN_ON_ERROR(co_await handleInitConfig(env,
table,
flat::NodeType::META,
meta::server::MetaServer::Config{},
store,
*configPath,
allowConfigExisted,
skipConfigCheck));
}
if (auto configPath = parser.present<String>("--storage"); configPath) {
CO_RETURN_ON_ERROR(co_await handleInitConfig(env,
table,
flat::NodeType::STORAGE,
storage::StorageServer::Config{},
store,
*configPath,
allowConfigExisted,
skipConfigCheck));
}
if (auto configPath = parser.present<String>("--fuse"); configPath) {
CO_RETURN_ON_ERROR(co_await handleInitConfig(env,
table,
flat::NodeType::FUSE,
fuse::FuseConfig{},
store,
*configPath,
allowConfigExisted,
skipConfigCheck));
}
co_return table;
}
} // namespace
CoTryTask<void> registerInitClusterHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleInitCluster);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerInitClusterHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,67 @@
#pragma once
#include <folly/Conv.h>
#include <optional>
#include "client/cli/common/Utils.h"
#include "common/utils/ArgParse.h"
#include "fbs/meta/Schema.h"
#include "fbs/mgmtd/ChainRef.h"
namespace hf3fs::client::cli {
inline void addLayoutArguments(argparse::ArgumentParser &parser) {
parser.add_argument("--chain-table-id").scan<'u', uint32_t>();
parser.add_argument("--chain-table-ver").scan<'u', uint32_t>();
parser.add_argument("--chain-list").help("Chain IDs separated by ',', eg: 1,2,3,4,5, can use with chain-table");
parser.add_argument("--chunk-size");
parser.add_argument("--stripe-size").scan<'u', uint32_t>();
}
inline std::optional<meta::Layout> parseLayout(const argparse::ArgumentParser &parser,
std::optional<meta::Layout> base = {}) {
auto chainTableId = parser.present<uint32_t>("--chain-table-id");
auto chainTableVersion = parser.present<uint32_t>("--chain-table-ver");
auto chainList = parser.present<std::string>("--chain-list");
std::optional<uint32_t> chunkSize;
if (auto l = parser.present<std::string>("--chunk-size")) {
chunkSize = Size::from(*l).value();
}
auto stripeSize = parser.present<uint32_t>("--stripe-size");
size_t args = chainTableId.has_value() + chainTableVersion.has_value() + chainList.has_value() +
chunkSize.has_value() + stripeSize.has_value();
if (args == 0) {
return std::nullopt;
}
if (base.has_value() && !chainList.has_value() && !chainTableId.has_value() && !chainTableVersion.has_value()) {
base->chunkSize = chunkSize.value_or(base->chunkSize);
base->stripeSize = stripeSize.value_or(base->stripeSize);
return *base;
}
ENSURE_USAGE(chunkSize.has_value() && (stripeSize.has_value() || chainList.has_value()) &&
(chainList.has_value() || chainTableId.has_value()));
if (chainList.has_value()) {
std::vector<std::string> idList;
folly::split(',', *chainList, idList, true);
ENSURE_USAGE(!idList.empty(), "chain-list is empty");
ENSURE_USAGE(!stripeSize.has_value() || *stripeSize == idList.size(),
fmt::format("chain-list and stripe-size not match, len({}) == {}, stripe {}",
*chainList,
idList.size(),
*stripeSize));
std::vector<uint32_t> chains;
for (const auto &id : idList) {
auto chain = folly::tryTo<uint32_t>(id);
ENSURE_USAGE(chain.hasValue(), fmt::format("Failed to parse chain-list {}, element {}", *chainList, id));
chains.push_back(*chain);
}
return meta::Layout::newChainList(flat::ChainTableId(chainTableId.value_or(0)),
flat::ChainTableVersion(chainTableVersion.value_or(0)),
*chunkSize,
std::move(chains));
} else {
return meta::Layout::newEmpty(flat::ChainTableId(*chainTableId), *chunkSize, *stripeSize);
}
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,70 @@
#include <fmt/chrono.h>
#include <string>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/MagicEnum.hpp"
#include "fmt/core.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("ls");
parser.add_argument("-l", "--limit").scan<'i', int>();
parser.add_argument("-s", "--status").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleList(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
Dispatcher::OutputTable table;
ENSURE_USAGE(args.size() <= 1);
auto path = args.empty() ? "." : args[0];
auto limit = parser.present<int>("-l");
auto status = parser.get<bool>("-s");
String prev;
for (;;) {
auto res = co_await env.metaClientGetter()
->list(env.userInfo, env.currentDirId, Path(path), prev, limit.value_or(0), status);
CO_RETURN_ON_ERROR(res);
const auto &lsp = res.value();
for (size_t i = 0; i < lsp.entries.size(); ++i) {
const auto &entry = lsp.entries.at(i);
if (!status) {
table.push_back({String(entry.name), String(magic_enum::enum_name(entry.type)), entry.id.toHexString()});
} else {
const auto &inode = lsp.inodes.at(i);
table.push_back({String(entry.name),
String(magic_enum::enum_name(entry.type)),
entry.id.toHexString(),
fmt::format("{}", inode.data().mtime),
inode.isFile() ? fmt::format("{}", inode.asFile().getVersionedLength())
: (inode.isDirectory() ? std::string("-") : inode.asSymlink().target.string()),
fmt::format("{:04o}", inode.acl.perm.toUnderType())});
}
}
if (limit.has_value()) {
limit.value() -= lsp.entries.size();
}
if (lsp.more && limit.value_or(1) > 0) {
prev = lsp.entries.at(lsp.entries.size() - 1).name;
} else {
break;
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerListHandler(Dispatcher &dispatcher) {
constexpr auto usage = "Usage: ls [path] [-l limit] [-s]";
co_return co_await dispatcher.registerHandler(usage, usage, getParser, handleList);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerListHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,54 @@
#include "ListChainTables.h"
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("list-chain-tables");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleListChainTables(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
XLOGF(DBG, "ListChainTables routingInfo:{}", serde::toJsonString(*routingInfo->raw()));
table.push_back({"ChainTableId", "ChainTableVersion", "ChainCount", "ReplicaCount", "Desc"});
for ([[maybe_unused]] const auto &[tableId, chainTables] : routingInfo->raw()->chainTables) {
for ([[maybe_unused]] const auto &[tv, chainTable] : chainTables) {
auto chainCount = chainTable.chains.size();
if (chainCount == 0) {
co_return makeError(StatusCode::kUnknown, fmt::format("Impossible: {} has no chains", tableId));
}
std::set<size_t> replicaCount;
for (auto i = 0u; i < chainCount; ++i) {
auto *ci = routingInfo->raw()->getChain(chainTable.chains[i]);
if (!ci) {
co_return makeError(MgmtdClientCode::kRoutingInfoNotReady);
}
replicaCount.insert(ci->targets.size());
}
table.push_back({std::to_string(tableId.toUnderType()),
std::to_string(tv.toUnderType()),
std::to_string(chainCount),
fmt::format("{}", fmt::join(replicaCount, "/")),
chainTable.desc});
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerListChainTablesHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleListChainTables);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerListChainTablesHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,165 @@
#include "ListChains.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("list-chains");
parser.add_argument("-t", "--table-id").scan<'u', uint32_t>();
parser.add_argument("-v", "--version").scan<'u', uint32_t>();
return parser;
}
void printChain(Dispatcher::OutputRow &row, const flat::ChainInfo &ci, const flat::RoutingInfo::TargetMap &targets) {
row.push_back(std::to_string(ci.chainVersion));
auto statusPos = row.size();
row.push_back("placeholder");
auto preferredOrder =
transformTo<std::vector>(std::span{ci.preferredTargetOrder.begin(), ci.preferredTargetOrder.size()},
[](auto tid) { return tid.toUnderType(); });
row.push_back(fmt::format("[{}]", fmt::join(preferredOrder, ",")));
size_t serving = 0, syncing = 0;
for (const auto &t : ci.targets) {
const auto &ti = targets.at(t.targetId);
row.push_back(fmt::format("{}({}-{})",
t.targetId.toUnderType(),
magic_enum::enum_name(ti.publicState),
magic_enum::enum_name(ti.localState)));
using PS = flat::PublicTargetState;
switch (ti.publicState) {
case PS::SERVING:
++serving;
break;
case PS::SYNCING:
++syncing;
break;
case PS::INVALID:
case PS::LASTSRV:
case PS::WAITING:
case PS::OFFLINE:
break;
}
}
auto &status = row[statusPos];
if (syncing) {
status = "SYNCING";
} else if (serving == ci.targets.size()) {
status = "SERVING";
} else if (serving == 0) {
status = "UNAVAILABLE";
} else {
status = fmt::format("SERVING({}/{})", serving, ci.targets.size());
}
}
void printChainTable(Dispatcher::OutputTable &table,
const flat::RoutingInfo::ChainMap &chains,
const flat::ChainTable &ct,
const flat::RoutingInfo::TargetMap &targets) {
for (const auto &c : ct.chains) {
const auto &ci = chains.at(c);
std::vector<String> v = {
fmt::format("{}", ci.chainId.toUnderType()),
std::to_string(ct.chainTableId.toUnderType()),
std::to_string(ct.chainTableVersion.toUnderType()),
};
printChain(v, ci, targets);
table.push_back(std::move(v));
}
}
CoTryTask<Dispatcher::OutputTable> handleListChains(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto tableId = parser.present<uint32_t>("-t");
auto tableVer = parser.present<uint32_t>("-v");
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
XLOGF(DBG, "ListChains routingInfo:{}", serde::toJsonString(*routingInfo->raw()));
const auto &chainTables = routingInfo->raw()->chainTables;
if (tableId) {
auto tbid = flat::ChainTableId(*tableId);
auto tbv = flat::ChainTableVersion(tableVer.value_or(0));
auto *ct = routingInfo->raw()->getChainTable(tbid, tbv);
if (!ct) co_return makeError(StatusCode::kInvalidArg, fmt::format("{} not found", tbid));
if (ct->chains.empty()) co_return makeError(StatusCode::kUnknown, fmt::format("{}@{} has no chains", tbid, tbv));
size_t replicaCount = 0;
for (auto cid : ct->chains) {
auto *ci = routingInfo->raw()->getChain(cid);
if (!ci) co_return makeError(StatusCode::kUnknown, fmt::format("{} not found", cid));
replicaCount = std::max(replicaCount, ci->targets.size());
}
auto head =
std::vector<String>{"ChainId", "ChainTable", "ChainTableVersion", "ChainVersion", "Status", "PreferredOrder"};
for (size_t i = 0; i < replicaCount; ++i) {
head.push_back("Target");
}
table.push_back(std::move(head));
printChainTable(table, routingInfo->raw()->chains, *ct, routingInfo->raw()->targets);
} else {
size_t maxTargets = 0;
for ([[maybe_unused]] const auto &[_, c] : routingInfo->raw()->chains)
maxTargets = std::max(maxTargets, c.targets.size());
auto head = std::vector<String>{"ChainId", "ReferencedBy", "ChainVersion", "Status", "PreferredOrder"};
for (size_t i = 0; i < maxTargets; ++i) head.push_back("Target");
table.push_back(std::move(head));
robin_hood::unordered_map<flat::ChainId, std::set<flat::ChainTableId::UnderlyingType>> refs;
for (const auto &[ctid, vm] : chainTables) {
for ([[maybe_unused]] const auto &[_, ct] : vm) {
for (const auto &cid : ct.chains) {
refs[cid].insert(ctid.toUnderType());
}
}
}
std::vector<flat::ChainInfo> chains;
for (const auto &[_, ci] : routingInfo->raw()->chains) {
chains.push_back(ci);
}
std::sort(chains.begin(), chains.end(), [](const auto &a, const auto &b) { return a.chainId < b.chainId; });
for (const auto &ci : chains) {
auto cid = ci.chainId;
Dispatcher::OutputRow row;
row.push_back(std::to_string(cid));
const auto &ts = refs[cid];
if (ts.empty()) {
row.push_back("N/A");
} else if (ts.size() == 1) {
row.push_back(std::to_string(*ts.begin()));
} else if (ts.size() == 2) {
row.push_back(fmt::format("{}", fmt::join(ts, ",")));
} else {
auto v = std::array{*ts.begin(), *++ts.begin()};
row.push_back(fmt::format("{},...", fmt::join(v, ",")));
}
printChain(row, ci, routingInfo->raw()->targets);
table.push_back(std::move(row));
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerListChainsHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleListChains);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerListChainsHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,109 @@
#include "ListClients.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("list-clients");
return parser;
}
void printClientSession(Dispatcher::OutputTable &table,
const flat::ClientSession &session,
const std::vector<flat::TagPair> &clientTags,
const flat::ConfigVersion *latestConfigVersion) {
Dispatcher::OutputRow row;
row.push_back(session.clientId);
row.push_back(session.clientStart.YmdHMS());
row.push_back(session.start.YmdHMS());
row.push_back(session.lastExtend.YmdHMS());
String configStatus = [&] {
if (latestConfigVersion && session.configVersion == *latestConfigVersion) {
switch (session.configStatus) {
case ConfigStatus::NORMAL:
return "UPTODATE";
case ConfigStatus::DIRTY:
return "DIRTY";
case ConfigStatus::FAILED:
return "FAILED";
default:
return "";
}
}
return "";
}();
if (configStatus.empty()) {
row.push_back(std::to_string(session.configVersion));
} else {
row.push_back(fmt::format("{}({})", session.configVersion.toUnderType(), configStatus));
}
row.push_back(session.universalId.empty() ? "N/A" : session.universalId);
row.push_back(session.description.empty() ? "N/A" : session.description);
row.push_back(fmt::format("[{}]", fmt::join(clientTags, ",")));
row.push_back(fmt::format("{}", session.releaseVersion));
table.push_back(std::move(row));
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto res = co_await env.mgmtdClientGetter()->listClientSessions();
CO_RETURN_ON_ERROR(res);
table.push_back({"ClientId",
"ClientStart",
"SessionStart",
"LastExtend",
"ConfigVersion",
"Hostname",
"Description",
"Tags",
"ReleaseVersion"});
auto configVersionsRes = co_await env.mgmtdClientGetter()->getConfigVersions();
const auto *latestConfigVersion =
configVersionsRes && configVersionsRes->contains("CLIENT") ? &configVersionsRes->at("CLIENT") : nullptr;
std::sort(res->sessions.begin(), res->sessions.end(), [](const flat::ClientSession &a, const flat::ClientSession &b) {
if (a.universalId != b.universalId) {
// clients without universalId have the lowest priority
if (a.universalId.empty() || b.universalId.empty()) return b.universalId.empty();
if (b.universalId.empty()) return true;
return a.universalId < b.universalId;
}
if (a.description != b.description) {
if (a.description.empty() || b.description.empty()) return b.description.empty();
return a.description < b.description;
}
return a.clientId < b.clientId;
});
for (const auto &session : res->sessions) {
printClientSession(table, session, res->referencedTags.at(session.universalId), latestConfigVersion);
}
if (res->bootstrapping) {
// TODO: directly print via printer
table.push_back({"Mgmtd Bootstrapping", "The client list may be incomplete"});
}
co_return table;
}
} // namespace
CoTryTask<void> registerListClientsHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerListClientsHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,62 @@
#include "ListGc.h"
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/Coroutine.h"
#include "common/utils/MagicEnum.hpp"
#include "common/utils/String.h"
#include "meta/components/GcManager.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("gc-list");
parser.add_argument("-l", "--limit").default_value(256).scan<'i', int>();
parser.add_argument("-p", "--prev");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleListGc(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
Dispatcher::OutputTable table;
ENSURE_USAGE(args.size() <= 1);
auto limit = parser.get<int>("-l");
auto prev = parser.present<std::string>("-p");
auto path = args.empty() ? "." : args[0];
auto res = co_await env.metaClientGetter()
->list(env.userInfo, meta::InodeId::gcRoot(), Path(path), prev.value_or(""), limit, false);
CO_RETURN_ON_ERROR(res);
const auto &lsp = res.value();
for (const auto &e : lsp.entries) {
auto parsed = meta::server::GcManager::parseGcEntry(e.name);
if (parsed.hasValue()) {
table.push_back({fmt::format("{}", parsed->first),
fmt::format("{}", parsed->second),
e.name,
e.id.toHexString(),
std::string(magic_enum::enum_name(e.type))});
} else {
table.push_back({String(e.name), e.id.toHexString()});
}
}
if (lsp.more) {
table.push_back({"..."});
}
co_return table;
}
} // namespace
CoTryTask<void> registerListGcHandler(Dispatcher &dispatcher) {
constexpr auto usage = "Usage: gc-list [path] [-l limit] [-p prev]";
co_return co_await dispatcher.registerHandler(usage, usage, getParser, handleListGc);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerListGcHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,93 @@
#include "ListNodes.h"
#include <fmt/chrono.h>
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/StringUtils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("list-nodes");
return parser;
}
void printNode(Dispatcher::OutputTable &table,
const flat::NodeInfo &node,
const RHStringHashMap<flat::ConfigVersion> *configVersions) {
auto nodeType = toStringView(node.type);
Dispatcher::OutputRow row;
row.push_back(fmt::format("{}", node.app.nodeId.toUnderType()));
row.push_back(String(nodeType));
row.push_back(String(magic_enum::enum_name(node.status)));
row.push_back(node.app.hostname);
row.push_back(fmt::format("{}", node.app.pid));
// TODO: print serviceGroups
row.push_back(fmt::format("[{}]", fmt::join(node.tags, ",")));
row.push_back(node.lastHeartbeatTs.toMicroseconds() ? node.lastHeartbeatTs.YmdHMS() : "N/A");
String configStatus = [&] {
if (configVersions && configVersions->contains(nodeType)) {
auto it = configVersions->find(nodeType);
if (node.configVersion == it->second) {
switch (node.configStatus) {
case ConfigStatus::NORMAL:
return "UPTODATE";
case ConfigStatus::DIRTY:
return "DIRTY";
case ConfigStatus::FAILED:
return "FAILED";
default:
return "";
}
}
}
return "";
}();
if (configStatus.empty()) {
row.push_back(std::to_string(node.configVersion));
} else {
row.push_back(fmt::format("{}({})", node.configVersion.toUnderType(), configStatus));
}
row.push_back(fmt::format("{}", node.app.releaseVersion));
table.push_back(std::move(row));
}
CoTryTask<Dispatcher::OutputTable> handleListNodes(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto mgmtdClient = env.mgmtdClientGetter();
CO_RETURN_ON_ERROR(co_await mgmtdClient->refreshRoutingInfo(/*force=*/true));
auto routingInfo = mgmtdClient->getRoutingInfo();
XLOGF(DBG, "ListNodes routingInfo:{}", serde::toJsonString(*routingInfo->raw()));
const auto &nodes = routingInfo->raw()->nodes;
table.push_back(
{"Id", "Type", "Status", "Hostname", "Pid", "Tags", "LastHeartbeatTime", "ConfigVersion", "ReleaseVersion"});
auto configVersionsRes = co_await mgmtdClient->getConfigVersions();
std::vector<flat::NodeInfo> nodeVec;
for ([[maybe_unused]] auto &[_, node] : nodes) nodeVec.push_back(std::move(node));
std::sort(nodeVec.begin(), nodeVec.end(), [](const flat::NodeInfo &a, const flat::NodeInfo &b) {
if (a.type != b.type) return a.type < b.type;
if (a.status != b.status) return a.status < b.status;
return a.app.nodeId < b.app.nodeId;
});
for (const auto &nodeInfo : nodeVec) printNode(table, nodeInfo, configVersionsRes ? &*configVersionsRes : nullptr);
co_return table;
}
} // namespace
CoTryTask<void> registerListNodesHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleListNodes);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerListNodesHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,104 @@
#include "ListTargets.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/StringUtils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("list-targets");
parser.add_argument("-c", "--chain-id").scan<'u', uint32_t>();
parser.add_argument("--orphan").default_value(false).implicit_value(true);
return parser;
}
Result<Void> printTargetsOfChain(Dispatcher::OutputTable &table,
const flat::ChainInfo &ci,
const flat::RoutingInfo::TargetMap &targets) {
auto n = ci.targets.size();
for (size_t i = 0; i < n; ++i) {
const auto &cti = ci.targets[i];
if (!targets.contains(cti.targetId)) {
return makeError(StatusCode::kUnknown,
fmt::format("{} of {} not found in RoutingInfo", cti.targetId, ci.chainId));
}
const auto &ti = targets.at(cti.targetId);
auto role = i == 0 ? "HEAD" : (i == n - 1 ? "TAIL" : "MIDDLE");
table.push_back({std::to_string(cti.targetId),
std::to_string(ci.chainId),
role,
toString(ti.publicState),
toString(ti.localState),
ti.nodeId ? std::to_string(*ti.nodeId) : "N/A",
ti.diskIndex ? std::to_string(*ti.diskIndex) : "N/A",
std::to_string(ti.usedSize)});
}
return Void{};
}
Result<Void> printOrphanTargets(Dispatcher::OutputTable &table, const std::vector<flat::TargetInfo> &targets) {
table.push_back({"TargetId", "LocalState", "NodeId", "DiskIndex", "UsedSize"});
for (const auto &ti : targets) {
table.push_back({std::to_string(ti.targetId),
toString(ti.localState),
ti.nodeId ? std::to_string(*ti.nodeId) : "N/A",
ti.diskIndex ? std::to_string(*ti.diskIndex) : "N/A",
std::to_string(ti.usedSize)});
}
return Void{};
}
CoTryTask<Dispatcher::OutputTable> handleListTargets(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto orphan = parser.get<bool>("--orphan");
if (orphan) {
auto rsp = co_await env.mgmtdClientGetter()->listOrphanTargets();
CO_RETURN_ON_ERROR(rsp);
printOrphanTargets(table, rsp->targets);
} else {
auto chainId = parser.present<uint32_t>("-c");
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(/*force=*/true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
XLOGF(DBG, "ListTargets routingInfo:{}", serde::toJsonString(*routingInfo->raw()));
auto head = std::vector<String>{"TargetId",
"ChainId",
"Role",
"PublicState",
"LocalState",
"NodeId",
"DiskIndex",
"UsedSize"};
table.push_back(std::move(head));
if (chainId) {
auto cid = flat::ChainId(*chainId);
auto *ci = routingInfo->raw()->getChain(cid);
if (!ci) co_return makeError(StatusCode::kInvalidArg, fmt::format("{} not found", cid));
CO_RETURN_ON_ERROR(printTargetsOfChain(table, *ci, routingInfo->raw()->targets));
} else {
const auto &chains = routingInfo->raw()->chains;
const auto &targets = routingInfo->raw()->targets;
for (const auto &[_, ci] : chains) {
CO_RETURN_ON_ERROR(printTargetsOfChain(table, ci, targets));
}
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerListTargetsHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleListTargets);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerListTargetsHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,51 @@
#include "Mkdir.h"
#include <scn/scn.h>
#include "AdminEnv.h"
#include "client/cli/admin/Layout.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("mkdir");
parser.add_argument("path");
parser.add_argument("-p", "-r", "--recursive").default_value(false).implicit_value(true);
parser.add_argument("--perm");
addLayoutArguments(parser);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleMkdir(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto path = parser.get<std::string>("path");
auto recursive = parser.get<bool>("-r");
auto p = parser.present<String>("--perm");
meta::Permission perm(0755);
if (p) {
auto [r, v] = scn::scan_tuple<uint32_t>(*p, "{:o}");
if (!r) co_return makeError(StatusCode::kInvalidArg, fmt::format("invalid permission format: {}", r.error().msg()));
perm = meta::Permission(v);
}
auto layout = parseLayout(parser);
auto res =
co_await env.metaClientGetter()->mkdirs(env.userInfo, env.currentDirId, Path(path), perm, recursive, layout);
CO_RETURN_ON_ERROR(res);
co_return table;
}
} // namespace
CoTryTask<void> registerMkdirHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleMkdir);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerMkdirHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,64 @@
#include "OfflineTarget.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/RapidCsv.h"
#include "common/utils/Result.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("offline-target");
parser.add_argument("--node-id").scan<'u', flat::NodeId::UnderlyingType>();
parser.add_argument("--target-id").scan<'u', flat::TargetId::UnderlyingType>().required();
parser.add_argument("--force").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleOfflineTarget(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
storage::OfflineTargetReq req;
req.targetId = flat::TargetId(parser.get<flat::TargetId::UnderlyingType>("--target-id"));
req.force = parser.get<bool>("--force");
flat::NodeId nodeId{};
auto nodeIdResult = parser.present<flat::NodeId::UnderlyingType>("--node-id");
if (nodeIdResult.has_value()) {
nodeId = flat::NodeId{nodeIdResult.value()};
} else {
CO_RETURN_AND_LOG_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
if (routingInfo == nullptr || routingInfo->raw() == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto targetInfo = routingInfo->getTarget(req.targetId);
if (targetInfo.has_value() && targetInfo->nodeId.has_value()) {
nodeId = targetInfo->nodeId.value();
} else {
co_return makeError(StorageClientCode::kRoutingError, "node id is unknown");
}
}
auto client = env.storageClientGetter();
auto res = co_await client->offlineTarget(nodeId, req);
CO_RETURN_ON_ERROR(res);
table.push_back({fmt::format("Offline target {} of {} succeeded", req.targetId.toUnderType(), nodeId.toUnderType())});
co_return table;
}
} // namespace
CoTryTask<void> registerOfflineTargetHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleOfflineTarget);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerOfflineTargetHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,83 @@
#include "OpenRange.h"
#include <folly/experimental/coro/Collect.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("open-range");
parser.add_argument("prefix");
parser.add_argument("inclusive_start").scan<'i', int64_t>();
parser.add_argument("exclusive_end").scan<'i', int64_t>();
parser.add_argument("-c", "--concurrency").default_value(1).scan<'i', int>();
parser.add_argument("-r", "--round").default_value(1).scan<'i', int>();
return parser;
}
CoTask<Dispatcher::OutputTable> handleOpenSubRange(AdminEnv &env, const String &prefix, int64_t start, int64_t n) {
Dispatcher::OutputTable table;
for (auto i = 0; i < n; ++i) {
auto path = fmt::format("{}{}", prefix, start + i);
auto res = co_await env.metaClientGetter()->open(env.userInfo, env.currentDirId, Path(path), std::nullopt, 0);
if (res.hasError()) {
table.push_back({fmt::format("Error at {}", start + i), res.error().describe()});
}
}
co_return std::move(table);
}
CoTryTask<Dispatcher::OutputTable> handleOpenRange(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
auto prefix = parser.get<std::string>("prefix");
auto inclusiveStart = parser.get<int64_t>("inclusive_start");
auto exclusiveEnd = parser.get<int64_t>("exclusive_end");
auto concurrency = parser.get<int>("-c");
auto round = parser.get<int>("-r");
Dispatcher::OutputTable table;
auto total = exclusiveEnd - inclusiveStart;
auto succeeded = total * round;
for (auto r = 0; r < round; ++r) {
auto every = total / concurrency;
auto remain = total % concurrency;
std::vector<CoTask<Dispatcher::OutputTable>> tasks;
auto start = inclusiveStart;
for (auto i = 0; i < remain; ++i) {
tasks.push_back(handleOpenSubRange(env, prefix, start, every + 1));
start += every + 1;
}
for (auto i = remain; i < concurrency; ++i) {
tasks.push_back(handleOpenSubRange(env, prefix, start, every));
start += every;
}
auto res = co_await folly::coro::collectAllRange(std::move(tasks));
for (auto &t : res) {
for (auto &r : t) {
table.push_back(std::move(r));
}
succeeded -= t.size();
}
}
table.push_back({"Succeeded", std::to_string(succeeded)});
table.push_back({"Failed", std::to_string(total * round - succeeded)});
co_return table;
}
} // namespace
CoTryTask<void> registerOpenRangeHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleOpenRange);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerOpenRangeHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,66 @@
#include "ParseTargetMeta.h"
#include <folly/experimental/coro/BlockingWait.h>
#include <fstream>
#include "AdminEnv.h"
#include "client/cli/admin/FileWrapper.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "common/serde/Serde.h"
#include "common/utils/FileUtils.h"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("parse-target-meta");
parser.add_argument("path");
parser.add_argument("-o", "--output");
parser.add_argument("--format").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleParseTargetMeta(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
// 1. parse arguments.
Path src = parser.get<std::string>("path");
auto output = parser.present<std::string>("--output");
auto format = parser.get<bool>("--format");
auto readResult = loadFile(src);
CO_RETURN_AND_LOG_ON_ERROR(readResult);
std::map<storage::ChunkId, storage::ChunkMetadata> metas;
CO_RETURN_AND_LOG_ON_ERROR(serde::deserialize(metas, *readResult));
// 5. write to file.
std::ofstream outputFile;
if (output.has_value()) {
outputFile = std::ofstream{output.value()};
if (!outputFile) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("create output file {} failed", output.value()));
}
}
std::ostream &out = output.has_value() ? outputFile : std::cout;
out << serde::toJsonString(metas, false, format) << std::endl;
co_return table;
}
} // namespace
CoTryTask<void> registerParseTargetMetaHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleParseTargetMeta);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerParseTargetMetaHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,46 @@
#include "PruneSession.h"
#include <fmt/core.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/app/NodeId.h"
#include "common/utils/Result.h"
#include "meta/components/SessionManager.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("session-prune");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handlePruneSession(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
meta::server::SessionManager::Config config;
config.set_sync_on_prune_session(false);
meta::server::SessionManager manager(config,
flat::NodeId{},
env.kvEngineGetter(),
env.mgmtdClientGetter(),
{} /* todo: fix */);
auto result = co_await manager.pruneManually();
CO_RETURN_ON_ERROR(result);
table.push_back({"num sessions", fmt::format("{}", *result)});
co_return table;
}
} // namespace
CoTryTask<void> registerPruneSessionHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handlePruneSession);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerPruneSessionHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,128 @@
#include "QueryChunk.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/serde/Serde.h"
#include "common/utils/RapidCsv.h"
#include "common/utils/Result.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("query-chunk");
parser.add_argument("-c", "--chain-id").scan<'u', flat::ChainId::UnderlyingType>();
parser.add_argument("--chunk").default_value(std::string{});
parser.add_argument("--read").default_value(false).implicit_value(true);
parser.add_argument("--index").default_value(uint32_t{0}).scan<'u', uint32_t>();
parser.add_argument("--touch").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleQueryChunk(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto chunkResult = storage::ChunkId::fromString(parser.get<std::string>("--chunk"));
CO_RETURN_AND_LOG_ON_ERROR(chunkResult);
auto chunk = *chunkResult;
storage::QueryChunkReq req;
req.chunkId = chunk;
auto chainIdResult = parser.present<flat::ChainId::UnderlyingType>("-c");
if (chainIdResult.has_value()) {
req.chainId = flat::ChainId{chainIdResult.value()};
} else {
CO_RETURN_AND_LOG_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo()->raw();
if (routingInfo == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto metaChunk = meta::ChunkId::unpack(chunk.data());
auto statResult =
co_await env.metaClientGetter()->stat(env.userInfo, meta::InodeId(metaChunk.inode()), std::nullopt, false);
CO_RETURN_AND_LOG_ON_ERROR(statResult);
meta::Inode &inode = *statResult;
std::cout << fmt::format("stat: {}", serde::toJsonString(inode, false, true)) << std::endl;
if (!inode.isFile()) {
co_return makeError(StatusCode::kInvalidArg, "not a file");
}
const auto &file = inode.asFile();
auto offset = metaChunk.chunk() * file.layout.chunkSize;
auto chainResult = file.getChainId(inode, offset, *routingInfo, 0);
CO_RETURN_AND_LOG_ON_ERROR(chainResult);
req.chainId = *chainResult;
}
auto client = env.storageClientGetter();
auto res = co_await client->queryChunk(req);
CO_RETURN_AND_LOG_ON_ERROR(res);
std::cout << serde::toJsonString(*res, false, true) << std::endl;
auto chunkSize = 512_KB;
for (auto &info : *res) {
if (info.hasValue()) {
chunkSize = info->meta->innerFileId.chunkSize;
}
}
auto doRead = parser.get<bool>("--read");
if (doRead) {
auto index = parser.get<uint32_t>("--index");
auto rdmabufPool = net::RDMABufPool::create(64_MB, 4);
auto buffer = co_await rdmabufPool->allocate();
if (UNLIKELY(!buffer)) {
XLOGF(ERR, "allocate buffer failed");
co_return makeError(RPCCode::kRDMANoBuf);
}
auto readBuffer = storage::client::IOBuffer{buffer};
std::vector<storage::client::ReadIO> batch;
storage::client::ReadOptions readOptions;
readOptions.set_enableChecksum(true);
readOptions.targetSelection().set_mode(storage::client::TargetSelectionMode::ManualMode);
readOptions.targetSelection().set_targetIndex(index);
auto readIO = client->createReadIO(req.chainId, req.chunkId, 0, chunkSize, (uint8_t *)buffer.ptr(), &readBuffer);
batch.push_back(std::move(readIO));
auto result = co_await client->batchRead(batch, env.userInfo, readOptions);
CO_RETURN_AND_LOG_ON_ERROR(result);
fmt::println("result: {}", batch.front().result);
}
auto doTouch = parser.get<bool>("--touch");
if (doTouch) {
auto rdmabufPool = net::RDMABufPool::create(64_MB, 4);
auto buffer = co_await rdmabufPool->allocate();
if (UNLIKELY(!buffer)) {
XLOGF(ERR, "allocate buffer failed");
co_return makeError(RPCCode::kRDMANoBuf);
}
auto writeBuffer = storage::client::IOBuffer{buffer};
std::vector<storage::client::WriteIO> batch;
storage::client::WriteOptions writeOptions;
auto writeIO =
client->createWriteIO(req.chainId, req.chunkId, 0, 0, chunkSize, (uint8_t *)buffer.ptr(), &writeBuffer);
batch.push_back(std::move(writeIO));
auto result = co_await client->batchWrite(batch, env.userInfo, writeOptions);
CO_RETURN_AND_LOG_ON_ERROR(result);
fmt::println("result: {}", batch.front().result);
}
co_return table;
}
} // namespace
CoTryTask<void> registerQueryChunkHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleQueryChunk);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerQueryChunkHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,213 @@
#include "client/cli/admin/ReadBench.h"
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <folly/Random.h>
#include <folly/ScopeGuard.h>
#include <folly/executors/CPUThreadPoolExecutor.h>
#include <folly/executors/GlobalExecutor.h>
#include <folly/experimental/coro/BlockingWait.h>
#include <folly/experimental/coro/Collect.h>
#include <folly/experimental/coro/Invoke.h>
#include <fstream>
#include <sstream>
#include <thread>
#include "AdminEnv.h"
#include "client/cli/admin/FileWrapper.h"
#include "client/cli/admin/Layout.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "client/storage/TargetSelection.h"
#include "common/serde/Serde.h"
#include "common/utils/Duration.h"
#include "common/utils/MagicEnum.hpp"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "fbs/storage/Common.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("read-bench");
parser.add_argument("path");
parser.add_argument("--threads").default_value(uint32_t{16}).scan<'u', uint32_t>();
parser.add_argument("--coroutines").default_value(uint32_t{1}).scan<'u', uint32_t>();
parser.add_argument("--seconds").default_value(uint32_t{60}).scan<'u', uint32_t>();
parser.add_argument("--write").default_value(false).implicit_value(true);
parser.add_argument("--bs");
parser.add_argument("--iodepth");
parser.add_argument("--mode");
return parser;
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
// 1. parse arguments.
Path path = parser.get<std::string>("path");
auto threads = parser.get<uint32_t>("--threads");
auto coroutines = parser.get<uint32_t>("--coroutines");
auto deadline = RelativeTime::now() + 1_s * parser.get<uint32_t>("--seconds");
auto modeStr = parser.present<std::string>("--mode").value_or("Default");
auto mode = magic_enum::enum_cast<storage::client::TargetSelectionMode>(modeStr);
auto isWrite = parser.get<bool>("--write");
if (!mode) {
co_return makeError(StorageClientCode::kRoutingError, "mode is invalid");
}
size_t blockSize = 4_KB;
if (auto p = parser.present<std::string>("--bs")) {
auto result = Size::from(*p);
CO_RETURN_AND_LOG_ON_ERROR(result);
blockSize = *result;
}
size_t iodepth = 1024;
if (auto p = parser.present<std::string>("--iodepth")) {
auto result = Size::from(*p);
CO_RETURN_AND_LOG_ON_ERROR(result);
iodepth = *result;
}
auto pool = std::make_unique<folly::CPUThreadPoolExecutor>(std::make_pair(threads, threads),
std::make_shared<folly::NamedThreadFactory>("Pool"));
// 2. open files.
std::string prev;
std::vector<FileWrapper> files;
while (true) {
auto res = co_await env.metaClientGetter()->list(env.userInfo, env.currentDirId, path, prev, 0, false);
CO_RETURN_AND_LOG_ON_ERROR(res);
const auto &lsp = res.value();
for (const auto &entry : lsp.entries) {
if (entry.isFile()) {
auto openResult = co_await FileWrapper::openOrCreateFile(env, path / entry.name);
CO_RETURN_AND_LOG_ON_ERROR(openResult);
files.push_back(std::move(*openResult));
}
}
if (lsp.more) {
prev = lsp.entries.at(lsp.entries.size() - 1).name;
} else {
break;
}
}
// 3. read.
auto storageClient = env.storageClientGetter();
std::vector<CoTryTask<Void>> total;
std::atomic<size_t> readBytes{};
total.reserve(coroutines);
co_await env.mgmtdClientGetter()->refreshRoutingInfo(true);
auto rdmabufPool = net::RDMABufPool::create(64_MB, 512);
for (auto i = 0u; i < coroutines; ++i) {
total.push_back(folly::coro::co_invoke([&]() -> CoTryTask<Void> {
std::ofstream out("/dev/null");
std::vector<storage::client::ReadIO> readIOs;
std::vector<storage::client::WriteIO> writeIOs;
std::vector<storage::client::IOBuffer> buffers;
net::RDMABuf current;
storage::client::ReadOptions readOptions;
storage::client::WriteOptions writeOptions;
readOptions.targetSelection().set_mode(*mode);
readIOs.reserve(1024);
writeIOs.reserve(1024);
buffers.reserve(1024);
while (RelativeTime::now() <= deadline) {
readIOs.clear();
writeIOs.clear();
buffers.clear();
current = {};
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo()->raw();
if (UNLIKELY(routingInfo == nullptr)) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
for (auto i = 0u; i < iodepth; ++i) {
auto &file = files[folly::Random::rand32() % files.size()];
if (UNLIKELY(file.length() == 0)) {
continue;
}
auto chunkSize = file.file().layout.chunkSize;
auto offset = folly::Random::rand32() * blockSize % file.length();
auto chainResult = file.file().getChainId(file.inode(), offset, *routingInfo, 0);
CO_RETURN_AND_LOG_ON_ERROR(chainResult);
auto chunk =
file.file().getChunkId(file.inode().id, offset).then([](auto chunk) { return storage::ChunkId(chunk); });
CO_RETURN_AND_LOG_ON_ERROR(chunk);
auto l = std::min(file.file().length - offset, blockSize);
if (current.size() < l) {
current = co_await rdmabufPool->allocate();
if (UNLIKELY(!current)) {
XLOGF(FATAL, "allocate buffer failed");
}
buffers.emplace_back(current);
}
if (isWrite) {
writeIOs.push_back(storageClient->createWriteIO(*chainResult,
*chunk,
offset % chunkSize,
l,
chunkSize,
current.ptr(),
&buffers.back()));
} else {
readIOs.push_back(
storageClient
->createReadIO(*chainResult, *chunk, offset % chunkSize, l, current.ptr(), &buffers.back()));
}
current.advance(l);
}
if (isWrite) {
auto writeResult = co_await storageClient->batchWrite(writeIOs, env.userInfo, writeOptions);
CO_RETURN_AND_LOG_ON_ERROR(writeResult);
} else {
auto readResult = co_await storageClient->batchRead(readIOs, env.userInfo, readOptions);
CO_RETURN_AND_LOG_ON_ERROR(readResult);
}
size_t succBytes = 0;
for (auto &io : readIOs) {
if (LIKELY(bool(io.result.lengthInfo))) {
succBytes += *io.result.lengthInfo;
}
}
for (auto &io : writeIOs) {
if (LIKELY(bool(io.result.lengthInfo))) {
succBytes += *io.result.lengthInfo;
}
}
readBytes += succBytes;
}
co_return Void{};
}));
}
std::atomic<bool> stop = false;
std::jthread t([&] {
while (!stop) {
std::this_thread::sleep_for(1_s);
XLOGF(WARNING, "{}/s", Size::around(readBytes.exchange(0)));
}
});
auto results = co_await folly::coro::collectAllRange(std::move(total)).scheduleOn(pool.get());
for (auto result : results) {
CO_RETURN_AND_LOG_ON_ERROR(result);
}
stop = true;
co_return table;
}
} // namespace
CoTryTask<void> registerReadBenchHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerReadBenchHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,135 @@
#include "ReadFile.h"
#include <folly/experimental/coro/BlockingWait.h>
#include <fstream>
#define OPENSSL_SUPPRESS_DEPRECATED
#include <openssl/md5.h>
#include "AdminEnv.h"
#include "client/cli/admin/FileWrapper.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
#include "client/storage/TargetSelection.h"
#include "common/serde/Serde.h"
#include "common/utils/Result.h"
#include "fbs/meta/Common.h"
#include "fbs/storage/Common.h"
#include "scn/scan/scan.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("read-file");
parser.add_argument("path");
parser.add_argument("--stat").default_value(false).implicit_value(true);
parser.add_argument("--offset");
parser.add_argument("--length");
parser.add_argument("--mode");
parser.add_argument("--disableChecksum").default_value(false).implicit_value(true);
parser.add_argument("--fillZero").default_value(false).implicit_value(true);
parser.add_argument("--verbose").default_value(false).implicit_value(true);
parser.add_argument("-o", "--output");
parser.add_argument("--hex").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleReadFile(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
// 1. parse arguments.
Path src = parser.get<std::string>("path");
auto stat = parser.get<bool>("--stat");
auto enableChecksum = !parser.get<bool>("--disableChecksum");
auto fillZero = parser.get<bool>("--fillZero");
auto verbose = parser.get<bool>("--verbose");
auto output = parser.present<std::string>("--output");
auto hex = parser.get<bool>("--hex");
size_t offset = 0;
if (auto o = parser.present("--offset")) {
auto offsetResult = Size::from(*o);
CO_RETURN_AND_LOG_ON_ERROR(offsetResult);
offset = *offsetResult;
}
std::optional<size_t> lengthIn;
if (auto l = parser.present<std::string>("--length")) {
auto lengthResult = Size::from(*l);
CO_RETURN_AND_LOG_ON_ERROR(lengthResult);
lengthIn = *lengthResult;
}
storage::client::TargetSelectionMode mode = storage::client::TargetSelectionMode::Default;
uint32_t targetIndex = 0;
if (auto m = parser.present("--mode")) {
auto result = magic_enum::enum_cast<storage::client::TargetSelectionMode>(*m);
if (result) {
mode = *result;
} else if (auto idx = scn::scan_value<uint32_t>(*m)) {
mode = storage::client::TargetSelectionMode::ManualMode;
targetIndex = idx.value();
} else {
co_return makeError(StatusCode::kInvalidArg, fmt::format("invalid mode", *m));
}
}
// 2. refresh routing info.
CO_RETURN_AND_LOG_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo()->raw();
if (routingInfo == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
// 3. open file.
auto openResult = co_await FileWrapper::openOrCreateFile(env, src);
CO_RETURN_AND_LOG_ON_ERROR(openResult);
auto &file = *openResult;
// 4. calc chain and chunk id.
if (file.length() <= offset) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("offset {} exceed file length {}", offset, file.length()));
}
auto readLength = std::min(lengthIn.value_or(file.length()), file.length() - offset);
if (stat) {
file.showChunks(offset, readLength);
}
// 5. read file.
std::ofstream outputFile;
if (output.has_value()) {
outputFile = std::ofstream{output.value()};
if (!outputFile) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("create output file {} failed", output.value()));
}
}
std::ostream &out = output.has_value() ? outputFile : std::cout;
MD5_CTX md5;
int md5ret = MD5_Init(&md5);
if (UNLIKELY(md5ret != 1)) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("md5 init failed: {}", md5ret));
}
auto writeResult =
co_await file.readFile(out, readLength, offset, enableChecksum, hex, mode, &md5, fillZero, verbose, targetIndex);
CO_RETURN_AND_LOG_ON_ERROR(writeResult);
auto checksum = *writeResult;
std::array<uint8_t, MD5_DIGEST_LENGTH> md5out{};
md5ret = MD5_Final(md5out.data(), &md5);
if (UNLIKELY(md5ret != 1)) {
co_return makeError(StatusCode::kInvalidArg, fmt::format("md5 final failed: {}", md5ret));
}
std::cout << fmt::format("read length {}, checksum {}, md5 {:02x}\n", readLength, checksum, fmt::join(md5out, ""));
co_return table;
}
} // namespace
CoTryTask<void> registerReadFileHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleReadFile);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,10 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerReadFileHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,233 @@
#include "RecursiveChown.h"
#include <folly/experimental/coro/Collect.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#define LOG_WITH_STDOUT(level, ...) \
do { \
auto msg = fmt::format(__VA_ARGS__); \
XLOG(level, msg); \
fmt::print("{}\n", msg); \
} while (0)
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("recursive-chown");
parser.add_argument("path");
parser.add_argument("uid").scan<'u', uint32_t>();
parser.add_argument("gid").scan<'u', uint32_t>();
parser.add_argument("-t", "--threads").default_value(uint32_t(16)).scan<'u', uint32_t>();
parser.add_argument("-c", "--concurrency").default_value(uint32_t(2048)).scan<'u', uint32_t>();
parser.add_argument("--debug").default_value(false).implicit_value(true);
return parser;
}
struct TaskArg {
meta::InodeId ino;
Path path;
};
class RecursiveChownTask {
public:
RecursiveChownTask(AdminEnv &env,
Path path,
uint32_t uid,
uint32_t gid,
uint32_t threads_num,
uint32_t concurrency,
bool debug)
: env_(env),
path_(path),
uid_(uid),
gid_(gid),
exec_(threads_num),
listSemaphore_(concurrency),
chownSemaphore_(concurrency),
debug_(debug) {}
~RecursiveChownTask() { exec_.join(); }
CoTryTask<Dispatcher::OutputTable> run() {
auto res = co_await env_.metaClientGetter()->stat(env_.userInfo, env_.currentDirId, path_, false);
CO_RETURN_ON_ERROR(res);
auto ino = res->id;
++listTasks_;
co_await listSemaphore_.co_wait();
listDir(TaskArg{.ino = ino, .path = path_}).scheduleOn(&exec_).start();
int64_t listTasksSuccess = 0;
int64_t listTasksFailed = 0;
int64_t listTasks = 0;
int64_t chownTasksSuccess = 0;
int64_t chownTasksFailed = 0;
int64_t chownTasks = 0;
for (;;) {
listTasksSuccess = listTasksSuccess_.load(std::memory_order_acquire);
listTasksFailed = listTasksFailed_.load(std::memory_order_acquire);
listTasks = listTasks_.load(std::memory_order_acquire);
chownTasksSuccess = chownTasksSuccess_.load(std::memory_order_acquire);
chownTasksFailed = chownTasksFailed_.load(std::memory_order_acquire);
chownTasks = chownTasks_.load(std::memory_order_acquire);
if (listTasks == listTasksSuccess + listTasksFailed && chownTasks == chownTasksSuccess + chownTasksFailed) {
break;
}
LOG_WITH_STDOUT(INFO,
"listTasks: total={} success={} failed={}. chownTasks: total={} success={} failed={}",
listTasks,
listTasksSuccess,
listTasksFailed,
chownTasks,
chownTasksSuccess,
chownTasksFailed);
co_await folly::coro::sleep(std::chrono::seconds(1));
}
Dispatcher::OutputTable table;
table.push_back(
{"listTasks", "listTasksSuccess", "listTasksFailed", "chownTasks", "chownTasksSuccess", "chownTasksFailed"});
table.push_back({
std::to_string(listTasks),
std::to_string(listTasksSuccess),
std::to_string(listTasksFailed),
std::to_string(chownTasks),
std::to_string(chownTasksSuccess),
std::to_string(chownTasksFailed),
});
co_return table;
}
CoTask<void> listDir(TaskArg arg) {
SCOPE_EXIT { listSemaphore_.signal(); };
std::list<TaskArg> args;
co_await listDir(arg, args);
co_await listDir(args);
}
CoTask<void> listDir(std::list<TaskArg> &args) {
while (!args.empty()) {
auto &arg = args.front();
if (listSemaphore_.try_wait()) {
listDir(std::move(arg)).scheduleOn(&exec_).start();
} else {
co_await listDir(arg, args);
}
args.pop_front();
}
co_return;
}
CoTask<void> listDir(const TaskArg &arg, std::list<TaskArg> &args) {
auto metaClient = env_.metaClientGetter();
if (debug_) {
LOG_WITH_STDOUT(INFO, "Listing {} {}", arg.ino, arg.path);
}
String prev;
while (true) {
auto res = co_await metaClient->list(env_.userInfo, arg.ino, std::nullopt, prev, 0, false);
if (res.hasError()) {
LOG_WITH_STDOUT(CRITICAL, "List inode {} {} failed, {}", arg.ino, arg.path, res.error());
++listTasksFailed_;
co_return;
}
for (const auto &entry : res->entries) {
auto nextPath = arg.path / entry.name;
if (entry.isDirectory()) {
++listTasks_;
if (listSemaphore_.try_wait()) {
listDir(TaskArg{.ino = entry.id, .path = nextPath}).scheduleOn(&exec_).start();
} else {
args.push_back(TaskArg{.ino = entry.id, .path = nextPath});
}
} else {
++chownTasks_;
co_await chownSemaphore_.co_wait();
chown(TaskArg{.ino = entry.id, .path = nextPath}).scheduleOn(&exec_).start();
}
}
if (res->more) {
prev = res->entries.back().name;
} else {
break;
}
}
++listTasksSuccess_;
co_return;
}
CoTask<void> chown(TaskArg arg) {
SCOPE_EXIT { chownSemaphore_.signal(); };
auto metaClient = env_.metaClientGetter();
if (debug_) {
LOG_WITH_STDOUT(INFO, "Chowning {} {}", arg.ino, arg.path);
}
auto res = co_await metaClient->setPermission(env_.userInfo,
arg.ino,
std::nullopt,
false,
flat::Uid(uid_),
flat::Gid(gid_),
std::nullopt,
std::nullopt);
if (res.hasError()) {
LOG_WITH_STDOUT(CRITICAL, "Chown inode {} {} failed, {}", arg.ino, arg.path, res.error());
++chownTasksFailed_;
co_return;
}
++chownTasksSuccess_;
}
private:
AdminEnv &env_;
Path path_;
uint32_t uid_;
uint32_t gid_;
folly::CPUThreadPoolExecutor exec_;
folly::fibers::Semaphore listSemaphore_;
folly::fibers::Semaphore chownSemaphore_;
std::atomic<int64_t> listTasks_ = 0;
std::atomic<int64_t> listTasksSuccess_ = 0;
std::atomic<int64_t> listTasksFailed_ = 0;
std::atomic<int64_t> chownTasks_ = 0;
std::atomic<int64_t> chownTasksSuccess_ = 0;
std::atomic<int64_t> chownTasksFailed_ = 0;
bool debug_;
};
CoTryTask<Dispatcher::OutputTable> handleRecursiveChown(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Path path{parser.get<std::string>("path")};
auto uid = parser.get<uint32_t>("uid");
auto gid = parser.get<uint32_t>("gid");
auto threads_num = parser.get<uint32_t>("-t");
auto concurrency = parser.get<uint32_t>("-c");
auto debug = parser.get<bool>("--debug");
auto task = std::make_unique<RecursiveChownTask>(env, path, uid, gid, threads_num, concurrency, debug);
co_return co_await task->run();
}
} // namespace
CoTryTask<void> registerRecursiveChownHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleRecursiveChown);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerRecursiveChownHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,36 @@
#include "RefreshRoutingInfo.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("refresh-routing-info");
parser.add_argument("-f", "--force").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto force = parser.get<bool>("-f");
CO_RETURN_ON_ERROR(co_await env.unsafeMgmtdClientGetter()->refreshRoutingInfo(force));
co_return table;
}
} // namespace
CoTryTask<void> registerRefreshRoutingInfoHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerRefreshRoutingInfoHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,41 @@
#include "RegisterNode.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/StringUtils.h"
namespace hf3fs::client::cli {
namespace {
const std::set<String> typeChoices(magic_enum::enum_names<flat::NodeType>().begin(),
magic_enum::enum_names<flat::NodeType>().end());
auto getParser() {
argparse::ArgumentParser parser("register-node");
parser.add_argument("nodeId").scan<'u', uint32_t>();
parser.add_argument("type").help(fmt::format("choices : {}", fmt::join(typeChoices, " | ")));
return parser;
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto nodeId = flat::NodeId(parser.get<uint32_t>("nodeId"));
auto type = magic_enum::enum_cast<flat::NodeType>(parser.get<String>("type"));
ENSURE_USAGE(type.has_value(), fmt::format("invalid type: {}", parser.get<String>("type")));
CO_RETURN_ON_ERROR(co_await env.mgmtdClientGetter()->registerNode(env.userInfo, nodeId, *type));
co_return table;
}
} // namespace
CoTryTask<void> registerRegisterNodesHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerRegisterNodesHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,232 @@
#include "RemoteCall.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/FileUtils.h"
#include "common/utils/StringUtils.h"
#include "fbs/core/service/CoreServiceBase.h"
#include "fbs/meta/Service.h"
#include "fbs/mgmtd/MgmtdServiceBase.h"
#include "fbs/storage/Service.h"
namespace hf3fs::client::cli {
namespace {
using MgmtdBase = mgmtd::MgmtdServiceBase<>;
using CoreBase = core::CoreServiceBase<>;
using StorageBase = storage::StorageSerde<>;
using MetaBase = meta::MetaSerde<>;
const auto services =
std::array{MgmtdBase::kServiceName, CoreBase::kServiceName, StorageBase::kServiceName, MetaBase::kServiceName};
auto getParser() {
argparse::ArgumentParser parser("remote-call");
parser.add_argument("-n", "--node-id").scan<'u', uint32_t>();
parser.add_argument("-c", "--client-id");
parser.add_argument("-a", "--addr");
parser.add_argument("--primary").default_value(false).implicit_value(true);
parser.add_argument("-s", "--service-name").help(fmt::format("choices: {}", fmt::join(services, " | ")));
parser.add_argument("-m", "--method-name");
parser.add_argument("-i", "--method-id").scan<'i', int32_t>();
parser.add_argument("-j", "--input-json");
parser.add_argument("-f", "--input-json-file");
parser.add_argument("-o", "--output-file");
parser.add_argument("--list-methods").default_value(false).implicit_value(true);
return parser;
}
#define REMOTE_CALLER_ARGS serde::ClientContext &ctx, std::string_view input, bool prettyFormat
using RemoteCaller = std::function<CoTryTask<String>(REMOTE_CALLER_ARGS)>;
template <typename ServiceBase>
Result<Void> getRemoteCaller(RemoteCaller &caller, String &methodName, int32_t &methodId) {
Result<Void> result = Void{};
auto handler = [&](auto type) {
using Type = decltype(type);
if (Type::name == methodName || static_cast<int32_t>(Type::id) == methodId) {
// complete method name and id
methodName = Type::name;
methodId = static_cast<int32_t>(Type::id);
caller = [methodName](REMOTE_CALLER_ARGS) -> CoTryTask<String> {
auto req = typename Type::ReqType{};
auto fromRes = serde::fromJsonString(req, input);
if (fromRes.hasError()) {
co_return makeError(fromRes.error().code(),
fmt::format("Deserialize {} failed: {}", typeid(req).name(), fromRes.error().message()));
}
auto callRes = co_await ctx.template call<ServiceBase::kServiceNameWrapper,
Type::nameWrapper,
typename Type::ReqType,
typename Type::RspType,
ServiceBase::kServiceID,
Type::id>(req, nullptr, nullptr);
if (callRes.hasError()) {
co_return makeError(
callRes.error().code(),
fmt::format("Call {}::{} failed: {}", ServiceBase::kServiceName, Type::name, callRes.error().message()));
}
co_return serde::toJsonString(*callRes, /*sortKeys=*/false, prettyFormat);
};
return false;
}
return true;
};
auto matchNothing = refl::Helper::iterate<ServiceBase>(std::move(handler));
if (matchNothing) {
result = makeError(RPCCode::kInvalidMethodID,
fmt::format("Could not find method by name({})/id({})", methodName, methodId));
}
return result;
}
template <typename ServiceBase>
Result<Void> listMethods(Dispatcher::OutputTable &table) {
table.push_back({"MethodId", "MethodName"});
auto handler = [&](auto type) {
using Type = decltype(type);
table.push_back({std::to_string(Type::id), String(Type::name)});
return true;
};
refl::Helper::iterate<ServiceBase>(std::move(handler));
return Void{};
}
Result<String> loadInput(const String &inputJson, const String &inputFile) {
if (!inputJson.empty()) return inputJson;
return loadFile(inputFile);
}
CoTryTask<std::vector<net::Address>> getAddrs(Dispatcher::OutputTable &table,
AdminEnv &env,
const String &service,
std::optional<uint32_t> nodeId,
std::optional<String> clientId,
std::optional<String> addr,
bool primary) {
std::vector<net::Address> addrs;
if (nodeId) {
table.push_back({"NodeId", std::to_string(*nodeId)});
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto node = routingInfo->getNode(flat::NodeId(*nodeId));
if (!node) {
co_return makeError(MgmtdCode::kNodeNotFound);
}
addrs = node->extractAddresses(service);
} else if (clientId) {
table.push_back({"ClientId", *clientId});
auto clientSession = co_await env.mgmtdClientGetter()->getClientSession(*clientId);
CO_RETURN_ON_ERROR(clientSession);
if (clientSession->session) {
addrs = flat::extractAddresses(clientSession->session->serviceGroups, service);
} else {
co_return makeError(MgmtdCode::kClientSessionNotFound,
fmt::format("bootstrapping: {}", clientSession->bootstrapping ? "Yes" : "No"));
}
} else if (addr) {
table.push_back({"Address", *addr});
auto addrRes = net::Address::from(*addr);
CO_RETURN_ON_ERROR(addrRes);
addrs.push_back(*addrRes);
} else if (primary) {
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
auto nodes = routingInfo->getNodeBy(flat::selectNodeByStatus(flat::NodeStatus::PRIMARY_MGMTD));
if (nodes.empty()) {
co_return makeError(MgmtdClientCode::kPrimaryMgmtdNotFound);
} else if (nodes.size() > 1) {
co_return makeError(StatusCode::kUnknown, "Found multiple primary mgmtds");
}
table.push_back({"NodeId", std::to_string(nodes[0].app.nodeId)});
addrs = nodes[0].extractAddresses(service);
} else {
XLOGF(FATAL, "Should not reach here");
}
co_return addrs;
}
CoTryTask<Dispatcher::OutputTable> handle(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto nodeId = parser.present<uint32_t>("-n");
auto clientId = parser.present<String>("-c");
auto addr = parser.present<String>("-a");
auto primary = parser.get<bool>("--primary");
auto service = parser.present<String>("-s").value_or("");
auto methodName = parser.present<String>("-m").value_or("");
auto methodId = parser.present<int32_t>("-i").value_or(-1);
auto inputJson = parser.present<String>("-j").value_or("");
auto inputFile = parser.present<String>("-f").value_or("");
auto outputFile = parser.present<String>("-o").value_or("");
auto isListMethods = parser.get<bool>("--list-methods");
ENSURE_USAGE(nodeId.has_value() + clientId.has_value() + addr.has_value() + primary + isListMethods == 1,
"must and can only specify one of -n, -c, -a, --primary, and --list-methods");
#define DISPATCH(service, function, ...) \
if (service == MgmtdBase::kServiceName) { \
CO_RETURN_ON_ERROR(function<MgmtdBase>(__VA_ARGS__)); \
} else if (service == CoreBase::kServiceName) { \
CO_RETURN_ON_ERROR(function<CoreBase>(__VA_ARGS__)); \
} else if (service == StorageBase::kServiceName) { \
CO_RETURN_ON_ERROR(function<StorageBase>(__VA_ARGS__)); \
} else if (service == MetaBase::kServiceName) { \
CO_RETURN_ON_ERROR(function<MetaBase>(__VA_ARGS__)); \
} else { \
co_return makeError(StatusCode::kInvalidArg, fmt::format("Unknown service: {}", service)); \
}
if (isListMethods) {
DISPATCH(service, listMethods, table);
} else {
auto addrsRes = co_await getAddrs(table, env, service, nodeId, clientId, addr, primary);
CO_RETURN_ON_ERROR(addrsRes);
const auto &addrs = *addrsRes;
auto inputRes = loadInput(inputJson, inputFile);
CO_RETURN_ON_ERROR(inputRes);
RemoteCaller caller;
DISPATCH(service, getRemoteCaller, caller, methodName, methodId);
table.push_back({"Method", fmt::format("{}::{}({})", service, methodName, methodId)});
for (auto addr : addrs) {
auto ctx = env.clientGetter()->serdeCtx(addr);
auto outputRes = co_await caller(ctx, *inputRes, true);
if (outputRes.hasError()) {
table.push_back({"Addr", addr.toString(), outputRes.error().describe()});
} else {
table.push_back({"Addr", addr.toString(), "OK"});
if (!outputFile.empty()) {
auto res = storeToFile(outputFile, *outputRes);
CO_RETURN_ON_ERROR(res);
table.push_back({"OutputFile", outputFile});
} else {
fmt::print("{}\n", *outputRes);
}
break;
}
}
}
co_return table;
}
} // namespace
CoTryTask<void> registerRemoteCallHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handle);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerRemoteCallHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,35 @@
#include "Remove.h"
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("rm");
parser.add_argument("path");
parser.add_argument("-r", "--recursive").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleRemove(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
auto path = parser.get<std::string>("path");
auto recursive = parser.get<bool>("-r");
auto res = co_await env.metaClientGetter()->remove(env.userInfo, env.currentDirId, Path(path), recursive);
CO_RETURN_ON_ERROR(res);
co_return table;
}
} // namespace
CoTryTask<void> registerRemoveHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleRemove);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerRemoveHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,206 @@
#include <folly/logging/xlog.h>
#include <vector>
#include "AdminEnv.h"
#include "DumpInodes.h"
#include "FindOrphanedChunks.h"
#include "client/cli/admin/DumpChunkMeta.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/IEnv.h"
#include "client/cli/common/Utils.h"
#include "client/storage/StorageClient.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("remove-chunks");
parser.add_argument("-n", "--num-inodes-perfile").default_value(uint32_t{10'000'000}).scan<'u', uint32_t>();
parser.add_argument("-f", "--fdb-cluster-file").default_value(std::string{"./fdb.cluster"});
parser.add_argument("-i", "--inode-dir").default_value(std::string{"inodes2"});
parser.add_argument("-o", "--orphaned-path").default_value(std::string{"orphaned"});
parser.add_argument("-r", "--do-remove").default_value(false).implicit_value(true);
parser.add_argument("-q", "--parquet-format").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> removeChunks(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
ENSURE_USAGE(env.mgmtdClientGetter);
Dispatcher::OutputTable table;
// dump latest inodes
const auto &fdbClusterFile = parser.get<std::string>("fdb-cluster-file");
const auto &numInodesPerFile = parser.get<uint32_t>("num-inodes-perfile");
const auto &inodeDir = parser.get<std::string>("inode-dir");
const auto &parquetFormat = parser.get<bool>("parquet-format");
if (boost::filesystem::exists(inodeDir)) {
XLOGF(CRITICAL, "Output directory for inodes already exists: {}", inodeDir);
co_return makeError(StatusCode::kInvalidArg);
}
auto dumpRes = co_await dumpInodesFromFdb(fdbClusterFile, numInodesPerFile, inodeDir, parquetFormat);
if (!dumpRes) co_return makeError(dumpRes.error());
// load the inode dump
std::time_t inodeDumpTime(std::time(nullptr));
auto uniqInodeIds = co_await loadInodeFromFiles(inodeDir,
{},
uint32_t{std::max(1U, std::thread::hardware_concurrency() / 2)},
parquetFormat,
&inodeDumpTime);
if (!uniqInodeIds) co_return makeError(uniqInodeIds.error());
const auto &doRemove = parser.get<bool>("do-remove");
const auto &orphanedPath = parser.get<std::string>("orphaned-path");
std::vector<Path> orphanedChunkPaths = listFilesFromPath(orphanedPath);
size_t totalOrphanedChunks = 0;
size_t totalRemainingOrphanedChunks = 0;
size_t totalRemovedOrphanedChunks = 0;
XLOGF(CRITICAL, "Removing orphaned chunks from {} files in path: {}", orphanedChunkPaths.size(), orphanedPath);
for (size_t orphanedChunkPathIndex = 0; orphanedChunkPathIndex < orphanedChunkPaths.size();
orphanedChunkPathIndex++) {
auto orphanedChunkPath = orphanedChunkPaths[orphanedChunkPathIndex];
ChunkMetaTable orphanedChunkmeta;
// load orphaned chunks
bool ok = parquetFormat ? orphanedChunkmeta.loadFromParquetFile(orphanedChunkPath)
: orphanedChunkmeta.loadFromFile(orphanedChunkPath, true /*jsonFormat*/);
if (!ok) {
XLOGF(FATAL, "Failed to load orphaned chunks in file: {}", orphanedChunkPath);
co_return makeError(StatusCode::kIOError);
}
if (orphanedChunkmeta.timestamp >= inodeDumpTime) {
XLOGF(CRITICAL,
"Orphaned chunk metadata dump time '{:%c}' >= inode snapshot time '{:%c}', skipping file: {}",
fmt::localtime(orphanedChunkmeta.timestamp),
fmt::localtime(inodeDumpTime),
orphanedChunkPath);
co_return makeError(StatusCode::kInvalidArg);
}
totalOrphanedChunks += orphanedChunkmeta.chunks.size();
std::vector<uint8_t> readBuffer(orphanedChunkmeta.chunks.size());
auto ioBuffer = env.storageClientGetter()->registerIOBuffer(&readBuffer[0], readBuffer.size());
if (!ioBuffer) co_return makeError(ioBuffer.error());
std::vector<storage::client::ReadIO> readIOs;
readIOs.reserve(orphanedChunkmeta.chunks.size());
for (const auto &orphanedChunkRow : orphanedChunkmeta.chunks) {
auto metaChunkId = meta::ChunkId::unpack(orphanedChunkRow.chunkmeta.chunkId.data());
auto metaInodeId = metaChunkId.inode();
// double check the inode of orphaned chunks
if (uniqInodeIds->count(metaInodeId)) {
XLOGF(CRITICAL,
"Stop removing since inode {} of chunk {} still exists, file: {}",
metaInodeId,
metaChunkId,
orphanedChunkPath);
co_return makeError(StatusCode::kInvalidArg);
}
auto readIO = env.storageClientGetter()->createReadIO(orphanedChunkRow.chainId,
orphanedChunkRow.chunkmeta.chunkId,
0 /* offset*/,
1 /* length*/,
&readBuffer[readIOs.size()],
&(*ioBuffer));
readIOs.push_back(std::move(readIO));
}
XLOGF_IF(FATAL,
readIOs.size() != orphanedChunkmeta.chunks.size(),
"Num of read IOs {} not equal to num of orphaned chunks {}, file: {}",
readIOs.size(),
orphanedChunkmeta.chunks.size(),
orphanedChunkPath);
// send batch read request to check if the orphaned chunks still there
co_await env.storageClientGetter()->batchRead(readIOs, flat::UserInfo{});
std::vector<storage::client::RemoveChunksOp> removeOps;
removeOps.reserve(orphanedChunkmeta.chunks.size());
for (const auto &readIO : readIOs) {
if (readIO.statusCode() == StorageClientCode::kChunkNotFound) {
XLOGF(DBG, "Orphaned chunk {} on {} does not exist, skipping", readIO.chunkId, readIO.routingTarget.chainId);
} else {
auto removeOp = env.storageClientGetter()->createRemoveOp(readIO.routingTarget.chainId,
storage::ChunkId(readIO.chunkId),
storage::ChunkId(readIO.chunkId, 1),
1 /*maxNumChunkIdsToProcess*/);
removeOps.push_back(std::move(removeOp));
XLOGF_IF(WARN,
readIO.statusCode() != StatusCode::kOK,
"Failed to read orphaned chunk {} on {} with error {}, removing it anyway",
readIO.chunkId,
readIO.routingTarget.chainId,
readIO.status());
}
}
totalRemainingOrphanedChunks += removeOps.size();
XLOGF_IF(WARN,
(!removeOps.empty() && removeOps.size() < readIOs.size()),
"Only {} out of {} orphaned chunks on {} still exist, file: {}",
removeOps.size(),
readIOs.size(),
orphanedChunkmeta.chainId,
orphanedChunkPath);
if (!doRemove) {
XLOGF(WARN, "Skip removing {} orphaned chunks in dry run mode, file: {}", removeOps.size(), orphanedChunkPath);
continue;
}
// send batch remove request
co_await env.storageClientGetter()->removeChunks(removeOps, flat::UserInfo{});
size_t removedChunks = std::accumulate(removeOps.begin(), removeOps.end(), size_t{0}, [](size_t s, const auto &op) {
return s + op.numProcessedChunks();
});
totalRemovedOrphanedChunks += removedChunks;
XLOGF(WARN,
"#{}/{} Removed {}/{} orphaned chunks on {}, file: {}",
orphanedChunkPathIndex,
orphanedChunkPaths.size(),
removedChunks,
removeOps.size(),
orphanedChunkmeta.chainId,
orphanedChunkPath);
}
XLOGF(CRITICAL,
"In total removed {} of {} remaining orphaned chunks among {} chunks found in directory: {}",
totalRemovedOrphanedChunks,
totalRemainingOrphanedChunks,
totalOrphanedChunks,
orphanedPath);
co_return table;
}
} // namespace
CoTryTask<void> registerRemoveChunksHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, removeChunks);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerRemoveChunksHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,77 @@
#include "RemoveRange.h"
#include <folly/experimental/coro/Collect.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("remove-range");
parser.add_argument("prefix");
parser.add_argument("inclusive_start").scan<'i', int64_t>();
parser.add_argument("exclusive_end").scan<'i', int64_t>();
parser.add_argument("-c", "--concurrency").default_value(1).scan<'i', int>();
return parser;
}
CoTask<Dispatcher::OutputTable> handleRemoveSubRange(AdminEnv &env, const String &prefix, int64_t start, int64_t n) {
Dispatcher::OutputTable table;
for (auto i = 0; i < n; ++i) {
auto path = fmt::format("{}{}", prefix, start + i);
auto res = co_await env.metaClientGetter()->remove(env.userInfo, env.currentDirId, Path(path), false);
if (res.hasError()) {
table.push_back({fmt::format("Error at {}", start + i), res.error().describe()});
}
}
co_return std::move(table);
}
CoTryTask<Dispatcher::OutputTable> handleRemoveRange(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
auto prefix = parser.get<std::string>("prefix");
auto inclusiveStart = parser.get<int64_t>("inclusive_start");
auto exclusiveEnd = parser.get<int64_t>("exclusive_end");
auto concurrency = parser.get<int>("-c");
auto total = exclusiveEnd - inclusiveStart;
auto every = total / concurrency;
auto remain = total % concurrency;
std::vector<CoTask<Dispatcher::OutputTable>> tasks;
auto start = inclusiveStart;
for (auto i = 0; i < remain; ++i) {
tasks.push_back(handleRemoveSubRange(env, prefix, start, every + 1));
start += every + 1;
}
for (auto i = remain; i < concurrency; ++i) {
tasks.push_back(handleRemoveSubRange(env, prefix, start, every));
start += every;
}
auto res = co_await folly::coro::collectAllRange(std::move(tasks));
auto succeeded = total;
Dispatcher::OutputTable table;
for (auto &t : res) {
for (auto &r : t) {
table.push_back(std::move(r));
}
succeeded -= t.size();
}
table.push_back({"Succeeded", std::to_string(succeeded)});
table.push_back({"Failed", std::to_string(total - succeeded)});
co_return table;
}
} // namespace
CoTryTask<void> registerRemoveRangeHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleRemoveRange);
}
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common/utils/Coroutine.h"
namespace hf3fs::client::cli {
class Dispatcher;
CoTryTask<void> registerRemoveRangeHandler(Dispatcher &dispatcher);
} // namespace hf3fs::client::cli

View File

@@ -0,0 +1,64 @@
#include "RemoveTarget.h"
#include <folly/Conv.h>
#include "AdminEnv.h"
#include "client/cli/common/Dispatcher.h"
#include "client/cli/common/Utils.h"
#include "common/utils/RapidCsv.h"
#include "common/utils/Result.h"
namespace hf3fs::client::cli {
namespace {
auto getParser() {
argparse::ArgumentParser parser("remove-target");
parser.add_argument("--node-id").scan<'u', flat::NodeId::UnderlyingType>().required();
parser.add_argument("--target-id").scan<'u', flat::TargetId::UnderlyingType>().required();
parser.add_argument("--force").default_value(false).implicit_value(true);
return parser;
}
CoTryTask<Dispatcher::OutputTable> handleRemoveTarget(IEnv &ienv,
const argparse::ArgumentParser &parser,
const Dispatcher::Args &args) {
[[maybe_unused]] auto &env = dynamic_cast<AdminEnv &>(ienv);
ENSURE_USAGE(args.empty());
Dispatcher::OutputTable table;
storage::RemoveTargetReq req;
req.targetId = flat::TargetId(parser.get<flat::TargetId::UnderlyingType>("--target-id"));
req.force = parser.get<bool>("--force");
flat::NodeId nodeId{};
auto nodeIdResult = parser.present<flat::NodeId::UnderlyingType>("--node-id");
if (nodeIdResult.has_value()) {
nodeId = flat::NodeId{nodeIdResult.value()};
} else {
CO_RETURN_AND_LOG_ON_ERROR(co_await env.mgmtdClientGetter()->refreshRoutingInfo(true));
auto routingInfo = env.mgmtdClientGetter()->getRoutingInfo();
if (routingInfo == nullptr || routingInfo->raw() == nullptr) {
co_return makeError(StorageClientCode::kRoutingError, "routing info is null");
}
auto targetInfo = routingInfo->getTarget(req.targetId);
if (targetInfo.has_value() && targetInfo->nodeId.has_value()) {
nodeId = targetInfo->nodeId.value();
} else {
co_return makeError(StorageClientCode::kRoutingError, "node id is unknown");
}
}
auto client = env.storageClientGetter();
auto res = co_await client->removeTarget(nodeId, req);
CO_RETURN_ON_ERROR(res);
table.push_back({fmt::format("Remove target {} of {} succeeded", req.targetId.toUnderType(), nodeId.toUnderType())});
co_return table;
}
} // namespace
CoTryTask<void> registerRemoveTargetHandler(Dispatcher &dispatcher) {
co_return co_await dispatcher.registerHandler(getParser, handleRemoveTarget);
}
} // namespace hf3fs::client::cli

Some files were not shown because too many files have changed in this diff Show More