mirror of
https://github.com/deepseek-ai/3FS
synced 2025-06-26 18:16:45 +00:00
Initial commit
This commit is contained in:
6
src/client/CMakeLists.txt
Normal file
6
src/client/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
add_subdirectory(cli)
|
||||
add_subdirectory(bin)
|
||||
add_subdirectory(mgmtd)
|
||||
add_subdirectory(meta)
|
||||
add_subdirectory(storage)
|
||||
add_subdirectory(core)
|
||||
2
src/client/bin/CMakeLists.txt
Normal file
2
src/client/bin/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
target_add_bin(admin_cli "admin_cli.cc" admin-cli)
|
||||
|
||||
238
src/client/bin/admin_cli.cc
Normal file
238
src/client/bin/admin_cli.cc
Normal 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();
|
||||
}
|
||||
2
src/client/cli/CMakeLists.txt
Normal file
2
src/client/cli/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(admin)
|
||||
25
src/client/cli/admin/AdminEnv.h
Normal file
25
src/client/cli/admin/AdminEnv.h
Normal 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
|
||||
402
src/client/cli/admin/AdminUserCtrl.cc
Normal file
402
src/client/cli/admin/AdminUserCtrl.cc
Normal 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
|
||||
8
src/client/cli/admin/AdminUserCtrl.h
Normal file
8
src/client/cli/admin/AdminUserCtrl.h
Normal 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
|
||||
137
src/client/cli/admin/Bench.cc
Normal file
137
src/client/cli/admin/Bench.cc
Normal 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
|
||||
10
src/client/cli/admin/Bench.h
Normal file
10
src/client/cli/admin/Bench.h
Normal 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
|
||||
1
src/client/cli/admin/CMakeLists.txt
Normal file
1
src/client/cli/admin/CMakeLists.txt
Normal 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)
|
||||
75
src/client/cli/admin/Chdir.cc
Normal file
75
src/client/cli/admin/Chdir.cc
Normal 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
|
||||
8
src/client/cli/admin/Chdir.h
Normal file
8
src/client/cli/admin/Chdir.h
Normal 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
|
||||
169
src/client/cli/admin/Checksum.cc
Normal file
169
src/client/cli/admin/Checksum.cc
Normal 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
|
||||
10
src/client/cli/admin/Checksum.h
Normal file
10
src/client/cli/admin/Checksum.h
Normal 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
|
||||
51
src/client/cli/admin/Create.cc
Normal file
51
src/client/cli/admin/Create.cc
Normal 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
|
||||
8
src/client/cli/admin/Create.h
Normal file
8
src/client/cli/admin/Create.h
Normal 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
|
||||
81
src/client/cli/admin/CreateRange.cc
Normal file
81
src/client/cli/admin/CreateRange.cc
Normal 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
|
||||
8
src/client/cli/admin/CreateRange.h
Normal file
8
src/client/cli/admin/CreateRange.h
Normal 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
|
||||
83
src/client/cli/admin/CreateTarget.cc
Normal file
83
src/client/cli/admin/CreateTarget.cc
Normal 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
|
||||
8
src/client/cli/admin/CreateTarget.h
Normal file
8
src/client/cli/admin/CreateTarget.h
Normal 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
|
||||
80
src/client/cli/admin/CreateTargets.cc
Normal file
80
src/client/cli/admin/CreateTargets.cc
Normal 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
|
||||
10
src/client/cli/admin/CreateTargets.h
Normal file
10
src/client/cli/admin/CreateTargets.h
Normal 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
|
||||
36
src/client/cli/admin/DecodeUserToken.cc
Normal file
36
src/client/cli/admin/DecodeUserToken.cc
Normal 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
|
||||
8
src/client/cli/admin/DecodeUserToken.h
Normal file
8
src/client/cli/admin/DecodeUserToken.h
Normal 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
|
||||
77
src/client/cli/admin/DropUserCache.cc
Normal file
77
src/client/cli/admin/DropUserCache.cc
Normal 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
|
||||
8
src/client/cli/admin/DropUserCache.h
Normal file
8
src/client/cli/admin/DropUserCache.h
Normal 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
|
||||
53
src/client/cli/admin/DumpChainTable.cc
Normal file
53
src/client/cli/admin/DumpChainTable.cc
Normal 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
|
||||
8
src/client/cli/admin/DumpChainTable.h
Normal file
8
src/client/cli/admin/DumpChainTable.h
Normal 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
|
||||
59
src/client/cli/admin/DumpChains.cc
Normal file
59
src/client/cli/admin/DumpChains.cc
Normal 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
|
||||
8
src/client/cli/admin/DumpChains.h
Normal file
8
src/client/cli/admin/DumpChains.h
Normal 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
|
||||
262
src/client/cli/admin/DumpChunkMeta.cc
Normal file
262
src/client/cli/admin/DumpChunkMeta.cc
Normal 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
|
||||
36
src/client/cli/admin/DumpChunkMeta.h
Normal file
36
src/client/cli/admin/DumpChunkMeta.h
Normal 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
|
||||
164
src/client/cli/admin/DumpDirEntries.cc
Normal file
164
src/client/cli/admin/DumpDirEntries.cc
Normal 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
|
||||
30
src/client/cli/admin/DumpDirEntries.h
Normal file
30
src/client/cli/admin/DumpDirEntries.h
Normal 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
|
||||
341
src/client/cli/admin/DumpInodes.cc
Normal file
341
src/client/cli/admin/DumpInodes.cc
Normal 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
|
||||
44
src/client/cli/admin/DumpInodes.h
Normal file
44
src/client/cli/admin/DumpInodes.h
Normal 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
|
||||
89
src/client/cli/admin/DumpSession.cc
Normal file
89
src/client/cli/admin/DumpSession.cc
Normal 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
|
||||
8
src/client/cli/admin/DumpSession.h
Normal file
8
src/client/cli/admin/DumpSession.h
Normal 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
|
||||
287
src/client/cli/admin/FileWrapper.cc
Normal file
287
src/client/cli/admin/FileWrapper.cc
Normal 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
|
||||
83
src/client/cli/admin/FileWrapper.h
Normal file
83
src/client/cli/admin/FileWrapper.h
Normal 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
|
||||
123
src/client/cli/admin/FillZero.cc
Normal file
123
src/client/cli/admin/FillZero.cc
Normal 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
|
||||
10
src/client/cli/admin/FillZero.h
Normal file
10
src/client/cli/admin/FillZero.h
Normal 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
|
||||
183
src/client/cli/admin/FindOrphanedChunks.cc
Normal file
183
src/client/cli/admin/FindOrphanedChunks.cc
Normal 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
|
||||
10
src/client/cli/admin/FindOrphanedChunks.h
Normal file
10
src/client/cli/admin/FindOrphanedChunks.h
Normal 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
|
||||
180
src/client/cli/admin/GetConfig.cc
Normal file
180
src/client/cli/admin/GetConfig.cc
Normal 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
|
||||
8
src/client/cli/admin/GetConfig.h
Normal file
8
src/client/cli/admin/GetConfig.h
Normal 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
|
||||
83
src/client/cli/admin/GetLastConfigUpdateRecord.cc
Normal file
83
src/client/cli/admin/GetLastConfigUpdateRecord.cc
Normal 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
|
||||
8
src/client/cli/admin/GetLastConfigUpdateRecord.h
Normal file
8
src/client/cli/admin/GetLastConfigUpdateRecord.h
Normal 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
|
||||
35
src/client/cli/admin/GetRealPath.cc
Normal file
35
src/client/cli/admin/GetRealPath.cc
Normal 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
|
||||
8
src/client/cli/admin/GetRealPath.h
Normal file
8
src/client/cli/admin/GetRealPath.h
Normal 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
|
||||
121
src/client/cli/admin/HotUpdateConfig.cc
Normal file
121
src/client/cli/admin/HotUpdateConfig.cc
Normal 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
|
||||
8
src/client/cli/admin/HotUpdateConfig.h
Normal file
8
src/client/cli/admin/HotUpdateConfig.h
Normal 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
|
||||
182
src/client/cli/admin/InitCluster.cc
Normal file
182
src/client/cli/admin/InitCluster.cc
Normal 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
|
||||
8
src/client/cli/admin/InitCluster.h
Normal file
8
src/client/cli/admin/InitCluster.h
Normal 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
|
||||
67
src/client/cli/admin/Layout.h
Normal file
67
src/client/cli/admin/Layout.h
Normal 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
|
||||
70
src/client/cli/admin/List.cc
Normal file
70
src/client/cli/admin/List.cc
Normal 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
|
||||
8
src/client/cli/admin/List.h
Normal file
8
src/client/cli/admin/List.h
Normal 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
|
||||
54
src/client/cli/admin/ListChainTables.cc
Normal file
54
src/client/cli/admin/ListChainTables.cc
Normal 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
|
||||
8
src/client/cli/admin/ListChainTables.h
Normal file
8
src/client/cli/admin/ListChainTables.h
Normal 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
|
||||
165
src/client/cli/admin/ListChains.cc
Normal file
165
src/client/cli/admin/ListChains.cc
Normal 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
|
||||
8
src/client/cli/admin/ListChains.h
Normal file
8
src/client/cli/admin/ListChains.h
Normal 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
|
||||
109
src/client/cli/admin/ListClients.cc
Normal file
109
src/client/cli/admin/ListClients.cc
Normal 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
|
||||
8
src/client/cli/admin/ListClients.h
Normal file
8
src/client/cli/admin/ListClients.h
Normal 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
|
||||
62
src/client/cli/admin/ListGc.cc
Normal file
62
src/client/cli/admin/ListGc.cc
Normal 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
|
||||
8
src/client/cli/admin/ListGc.h
Normal file
8
src/client/cli/admin/ListGc.h
Normal 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
|
||||
93
src/client/cli/admin/ListNodes.cc
Normal file
93
src/client/cli/admin/ListNodes.cc
Normal 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
|
||||
8
src/client/cli/admin/ListNodes.h
Normal file
8
src/client/cli/admin/ListNodes.h
Normal 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
|
||||
104
src/client/cli/admin/ListTargets.cc
Normal file
104
src/client/cli/admin/ListTargets.cc
Normal 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
|
||||
8
src/client/cli/admin/ListTargets.h
Normal file
8
src/client/cli/admin/ListTargets.h
Normal 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
|
||||
51
src/client/cli/admin/Mkdir.cc
Normal file
51
src/client/cli/admin/Mkdir.cc
Normal 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
|
||||
8
src/client/cli/admin/Mkdir.h
Normal file
8
src/client/cli/admin/Mkdir.h
Normal 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
|
||||
64
src/client/cli/admin/OfflineTarget.cc
Normal file
64
src/client/cli/admin/OfflineTarget.cc
Normal 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
|
||||
8
src/client/cli/admin/OfflineTarget.h
Normal file
8
src/client/cli/admin/OfflineTarget.h
Normal 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
|
||||
83
src/client/cli/admin/OpenRange.cc
Normal file
83
src/client/cli/admin/OpenRange.cc
Normal 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
|
||||
8
src/client/cli/admin/OpenRange.h
Normal file
8
src/client/cli/admin/OpenRange.h
Normal 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
|
||||
66
src/client/cli/admin/ParseTargetMeta.cc
Normal file
66
src/client/cli/admin/ParseTargetMeta.cc
Normal 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
|
||||
10
src/client/cli/admin/ParseTargetMeta.h
Normal file
10
src/client/cli/admin/ParseTargetMeta.h
Normal 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
|
||||
46
src/client/cli/admin/PruneSession.cc
Normal file
46
src/client/cli/admin/PruneSession.cc
Normal 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
|
||||
8
src/client/cli/admin/PruneSession.h
Normal file
8
src/client/cli/admin/PruneSession.h
Normal 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
|
||||
128
src/client/cli/admin/QueryChunk.cc
Normal file
128
src/client/cli/admin/QueryChunk.cc
Normal 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
|
||||
10
src/client/cli/admin/QueryChunk.h
Normal file
10
src/client/cli/admin/QueryChunk.h
Normal 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
|
||||
213
src/client/cli/admin/ReadBench.cc
Normal file
213
src/client/cli/admin/ReadBench.cc
Normal 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
|
||||
10
src/client/cli/admin/ReadBench.h
Normal file
10
src/client/cli/admin/ReadBench.h
Normal 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
|
||||
135
src/client/cli/admin/ReadFile.cc
Normal file
135
src/client/cli/admin/ReadFile.cc
Normal 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
|
||||
10
src/client/cli/admin/ReadFile.h
Normal file
10
src/client/cli/admin/ReadFile.h
Normal 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
|
||||
233
src/client/cli/admin/RecursiveChown.cc
Normal file
233
src/client/cli/admin/RecursiveChown.cc
Normal 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
|
||||
8
src/client/cli/admin/RecursiveChown.h
Normal file
8
src/client/cli/admin/RecursiveChown.h
Normal 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
|
||||
36
src/client/cli/admin/RefreshRoutingInfo.cc
Normal file
36
src/client/cli/admin/RefreshRoutingInfo.cc
Normal 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
|
||||
8
src/client/cli/admin/RefreshRoutingInfo.h
Normal file
8
src/client/cli/admin/RefreshRoutingInfo.h
Normal 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
|
||||
41
src/client/cli/admin/RegisterNode.cc
Normal file
41
src/client/cli/admin/RegisterNode.cc
Normal 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
|
||||
8
src/client/cli/admin/RegisterNode.h
Normal file
8
src/client/cli/admin/RegisterNode.h
Normal 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
|
||||
232
src/client/cli/admin/RemoteCall.cc
Normal file
232
src/client/cli/admin/RemoteCall.cc
Normal 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
|
||||
8
src/client/cli/admin/RemoteCall.h
Normal file
8
src/client/cli/admin/RemoteCall.h
Normal 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
|
||||
35
src/client/cli/admin/Remove.cc
Normal file
35
src/client/cli/admin/Remove.cc
Normal 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
|
||||
8
src/client/cli/admin/Remove.h
Normal file
8
src/client/cli/admin/Remove.h
Normal 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
|
||||
206
src/client/cli/admin/RemoveChunks.cc
Normal file
206
src/client/cli/admin/RemoveChunks.cc
Normal 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
|
||||
8
src/client/cli/admin/RemoveChunks.h
Normal file
8
src/client/cli/admin/RemoveChunks.h
Normal 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
|
||||
77
src/client/cli/admin/RemoveRange.cc
Normal file
77
src/client/cli/admin/RemoveRange.cc
Normal 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
|
||||
8
src/client/cli/admin/RemoveRange.h
Normal file
8
src/client/cli/admin/RemoveRange.h
Normal 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
|
||||
64
src/client/cli/admin/RemoveTarget.cc
Normal file
64
src/client/cli/admin/RemoveTarget.cc
Normal 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
Reference in New Issue
Block a user