Initial commit

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

18
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
add_subdirectory(fbs)
add_subdirectory(common)
add_subdirectory(meta)
add_subdirectory(client)
add_subdirectory(lib)
add_subdirectory(memory)
add_subdirectory(kv)
add_subdirectory(storage)
add_subdirectory(mgmtd)
add_subdirectory(fdb)
add_subdirectory(stubs)
add_subdirectory(monitor_collector)
add_subdirectory(analytics)
add_subdirectory(core)
add_subdirectory(tools)
add_subdirectory(fuse)
add_subdirectory(simple_example)
add_subdirectory(migration)

View File

@@ -0,0 +1 @@
target_add_lib(analytics common apache_arrow_static)

65
src/analytics/Common.h Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include "common/serde/Serde.h"
#include "common/utils/StrongType.h"
namespace hf3fs::serde {
template <typename T>
using SerdeToReadableMemberMethodReturnType = std::invoke_result_t<decltype(&T::serdeToReadable), T>;
template <typename T>
using SerdeToMemberMethodReturnType = std::invoke_result_t<decltype(&T::serdeTo), T>;
template <typename T>
using SerdeToReadableReturnType = std::invoke_result_t<decltype(serde::SerdeMethod<T>::serdeToReadable), const T &>;
template <typename T>
using SerdeToReturnType = std::invoke_result_t<decltype(serde::SerdeMethod<T>::serdeTo), const T &>;
template <typename T>
concept ConvertibleToString = std::is_convertible_v<T, std::string>;
template <typename T>
concept WithReadableSerdeMemberMethod =
!StrongTyped<T> && !ConvertibleToString<T> && requires(const T &t, SerdeToReadableMemberMethodReturnType<T> s) {
{ t.serdeToReadable() } -> std::convertible_to<SerdeToReadableMemberMethodReturnType<T>>;
{ T::serdeFromReadable(s) } -> std::convertible_to<Result<T>>;
};
template <typename T>
concept WithReadableSerdeMethod = !StrongTyped<T> && !ConvertibleToString<T> && !WithReadableSerdeMemberMethod<T> &&
requires(const T &t, SerdeToReadableReturnType<T> s) {
{ serde::SerdeMethod<T>::serdeToReadable(t) } -> std::convertible_to<SerdeToReadableReturnType<T>>;
{ serde::SerdeMethod<T>::serdeFromReadable(s) } -> std::convertible_to<Result<T>>;
};
template <typename T>
concept WithSerdeMemberMethod =
!StrongTyped<T> && !ConvertibleToString<T> && !WithReadableSerdeMemberMethod<T> && !WithReadableSerdeMethod<T> &&
requires(const T &t, SerdeToMemberMethodReturnType<T> s) {
{ t.serdeTo() } -> std::convertible_to<SerdeToMemberMethodReturnType<T>>;
{ T::serdeFrom(s) } -> std::convertible_to<Result<T>>;
};
template <typename T>
concept WithSerdeMethod =
!StrongTyped<T> && !ConvertibleToString<T> && !WithReadableSerdeMemberMethod<T> && !WithReadableSerdeMethod<T> &&
!WithSerdeMemberMethod<T> && requires(const T &t, SerdeToReturnType<T> s) {
{ serde::SerdeMethod<T>::serdeTo(t) } -> std::convertible_to<SerdeToReturnType<T>>;
{ serde::SerdeMethod<T>::serdeFrom(s) } -> std::convertible_to<Result<T>>;
};
template <typename T>
concept SerdeTypeWithoutSpecializedSerdeMethod =
serde::SerdeType<T> && !WithReadableSerdeMemberMethod<T> && !WithReadableSerdeMethod<T> &&
!WithSerdeMemberMethod<T> && !WithSerdeMethod<T>;
} // namespace hf3fs::serde
namespace hf3fs::analytics {
const std::string kVariantValueIndexColumnSuffix = "ValIdx";
const std::string kResultErrorTypeColumnSuffix = "Error";
} // namespace hf3fs::analytics

View File

@@ -0,0 +1,238 @@
#pragma once
#include <arrow/io/file.h>
#include <optional>
#include <parquet/stream_reader.h>
#include <type_traits>
#include <utility>
#include <variant>
#include "SerdeObjectVisitor.h"
#include "SerdeSchemaBuilder.h"
#include "common/serde/Serde.h"
#include "common/utils/Nameof.hpp"
#include "common/utils/StrongType.h"
#include "common/utils/TypeTraits.h"
namespace hf3fs::analytics {
template <serde::SerdeType SerdeType>
class SerdeObjectReader : public BaseObjectVisitor<SerdeObjectReader<SerdeType>> {
public:
SerdeObjectReader(parquet::StreamReader &&reader)
: reader_(std::move(reader)) {}
static std::shared_ptr<SerdeObjectReader> open(const Path path) {
// open file
auto openStream = arrow::io::ReadableFile::Open(path.string());
if (!openStream.ok()) {
XLOGF(ERR, "Failed to open file input stream: {}, error: {}", path.string(), openStream.status().message());
return nullptr;
}
std::shared_ptr<arrow::io::ReadableFile> infile;
PARQUET_ASSIGN_OR_THROW(infile, openStream);
try {
parquet::StreamReader streamReader{parquet::ParquetFileReader::Open(infile)};
return std::make_shared<SerdeObjectReader>(std::move(streamReader));
} catch (const std::exception &ex) {
XLOGF(ERR, "Failed to create stream reader: {}, error: {}", path.string(), ex.what());
return nullptr;
}
}
SerdeObjectReader &operator>>(SerdeType &v) {
eof_ = eof();
if (!bool(*this)) return *this;
try {
visit("", v);
reader_ >> parquet::EndRow;
} catch (const parquet::ParquetException &ex) {
XLOGF(CRITICAL, "Failed to read from parquet file, error: {}", ex.what());
isOk_ = false;
}
return *this;
}
operator bool() const { return ok() && !eof_; }
bool ok() const { return isOk_; }
bool eof() const { return reader_.eof(); }
size_t numRows() const { return reader_.num_rows(); }
public:
// default
template <typename T>
void visit(std::string_view k, T &v) = delete;
template <typename T>
requires std::is_arithmetic_v<T>
void visit(std::string_view k, T &v) {
reader_ >> v;
XLOGF(DBG3, "arithmetic visit({}): {}", k, v);
}
template <typename T>
requires std::is_enum_v<T>
void visit(std::string_view k, T &v) {
int32_t n;
reader_ >> n;
XLOGF(DBG3, "enum visit({}): {}", k, n);
auto result = magic_enum::enum_cast<T>(n);
if (result) {
v = *result;
} else {
XLOGF(CRITICAL, "Failed to parse enum {} from value: {}", nameof::nameof_short_type<T>(), n);
}
}
template <serde::ConvertibleToString T>
void visit(std::string_view k, T &v) {
reader_ >> v;
XLOGF(DBG3, "string visit({}): {}", k, v);
}
template <StrongTyped T>
void visit(std::string_view k, T &v) {
XLOGF(DBG3, "strongtyped visit({})", k);
BaseObjectVisitor<SerdeObjectReader>::visit(k, v);
}
template <serde::WithReadableSerdeMethod T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "WithReadableSerdeMethod visit({})", k);
typename serde::SerdeToReadableReturnType<T> serialized;
visit(k, serialized);
auto result = serde::SerdeMethod<T>::serdeFromReadable(serialized);
if (result) {
val = *result;
} else {
XLOGF(CRITICAL, "Failed to parse {} from value: {}", nameof::nameof_short_type<T>(), serialized);
}
}
template <serde::WithSerdeMethod T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "WithSerdeMethod visit({})", k);
typename serde::SerdeToReturnType<T> serialized;
visit(k, serialized);
auto result = serde::SerdeMethod<T>::serdeFrom(serialized);
if (result) {
val = *result;
} else {
XLOGF(CRITICAL, "Failed to parse {} from value: {}", nameof::nameof_short_type<T>(), serialized);
}
}
template <serde::WithReadableSerdeMemberMethod T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "WithReadableSerdeMemberMethod visit({})", k);
serde::SerdeToReadableMemberMethodReturnType<T> serialized;
visit(k, serialized);
auto result = T::serdeFromReadable(serialized);
if (result) {
val = *result;
} else {
XLOGF(CRITICAL, "Failed to parse {} from value: {}", nameof::nameof_short_type<T>(), serialized);
}
}
template <serde::WithSerdeMemberMethod T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "WithSerdeMemberMethod visit({})", k);
serde::SerdeToReadableMemberMethodReturnType<T> serialized;
visit(k, serialized);
auto result = T::serdeFromReadable(serialized);
if (result) {
val = *result;
} else {
XLOGF(CRITICAL, "Failed to parse {} from value: {}", nameof::nameof_short_type<T>(), serialized);
}
}
template <serde::SerdeTypeWithoutSpecializedSerdeMethod T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "serdetype visit({})", k);
BaseObjectVisitor<SerdeObjectReader>::visit(k, val);
}
template <typename T>
requires is_specialization_of_v<T, folly::Expected>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "result visit({})", k);
std::string errorColumnName = std::string{k} + kResultErrorTypeColumnSuffix;
Status status(StatusCode::kOK);
typename T::value_type value;
visit<typename T::error_type>(errorColumnName, status);
visit<typename T::value_type>(k, value);
if (status.isOK()) {
val = std::move(value);
} else {
val = makeError(status);
}
}
template <typename T>
requires is_variant_v<T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "variant visit({})", k);
// get the index of value
std::string valIdxColumnName = std::string{k} + kVariantValueIndexColumnSuffix;
uint32_t valIdx = 0;
visit<uint32_t>(valIdxColumnName, valIdx);
// read and set the value
uint32_t altIdx = 0;
visitVariant(val, [&](std::string_view typeName, auto &&v) {
std::string altTypeName = std::string{k} + std::string{typeName};
std::remove_reference_t<decltype(v)> alt;
visit(altTypeName, alt);
if (altIdx == valIdx) val = std::move(alt);
altIdx++;
});
}
template <typename T>
requires is_vector_v<T> || is_set_v<T>
void visit(std::string_view k, T &val) {
std::string str;
reader_ >> str;
XLOGF(DBG3, "container visit({}): {}", k, str);
auto result = serde::fromJsonString(val, str);
if (!result) {
XLOGF(CRITICAL, "Failed to parse {} from json string: {}", nameof::nameof_short_type<T>(), str);
}
}
template <typename T>
requires is_optional_v<T>
void visit(std::string_view k, T &val) {
std::string str;
reader_ >> str;
XLOGF(DBG3, "container visit({}): {}", k, str);
if (str.empty()) {
val = std::nullopt;
} else {
using ValueType = typename T::value_type;
val = ValueType();
auto result = serde::fromJsonString(*val, str);
if (!result) {
XLOGF(CRITICAL, "Failed to parse {} from json string: {}", nameof::nameof_short_type<ValueType>(), str);
}
}
}
private:
parquet::StreamReader reader_;
bool isOk_{true};
bool eof_{false};
};
} // namespace hf3fs::analytics

View File

@@ -0,0 +1,129 @@
#pragma once
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include "Common.h"
#include "common/serde/Serde.h"
#include "common/utils/Nameof.hpp"
#include "common/utils/StrongType.h"
#include "common/utils/TypeTraits.h"
namespace hf3fs::analytics {
class ObjectVisitor {
public:
// default
template <typename T>
void visit(std::string_view, T &) = delete;
template <typename T>
requires std::is_arithmetic_v<T>
void visit(std::string_view, T &) = delete;
template <typename T>
requires std::is_enum_v<T>
void visit(std::string_view, T &) = delete;
template <StrongTyped T>
void visit(std::string_view, T &) = delete;
template <serde::ConvertibleToString T>
void visit(std::string_view, T &) = delete;
template <serde::SerdeTypeWithoutSpecializedSerdeMethod T>
void visit(std::string_view, T &) = delete;
template <typename T>
requires is_variant_v<T>
void visit(std::string_view, T &) = delete;
template <typename T>
requires is_vector_v<T> || is_set_v<T>
void visit(std::string_view, T &) = delete;
template <typename T>
requires is_optional_v<T>
void visit(std::string_view, T &) = delete;
};
template <size_t I = 0>
inline void visitVariant(auto &&t, auto &&func) {
using T = std::decay_t<decltype(t)>;
using S = std::variant_alternative_t<I, T>;
if (t.index() == I) {
func(nameof::nameof_short_type<S>(), std::get<I>(t));
} else {
func(nameof::nameof_short_type<S>(), std::variant_alternative_t<I, T>{});
}
if constexpr (I + 1 < std::variant_size_v<T>) {
visitVariant<I + 1>(t, func);
}
}
template <typename Derived>
class BaseObjectVisitor : public ObjectVisitor {
public:
template <typename T>
void visit(std::string_view k, T &) = delete;
template <typename T>
requires std::is_arithmetic_v<T>
void visit(std::string_view k, T &v) {
XLOGF(DBG3, "arithmetic visit({})", k);
static_cast<Derived *>(this)->template visit<T>(k, v);
}
template <typename T>
requires std::is_enum_v<T>
void visit(std::string_view k, T &) = delete;
template <serde::ConvertibleToString T>
void visit(std::string_view k, T &) = delete;
template <StrongTyped T>
void visit(std::string_view k, T &v) {
XLOGF(DBG3, "strongtyped visit({})", k);
static_cast<Derived *>(this)->template visit<typename T::UnderlyingType>(k, v);
}
template <serde::SerdeTypeWithoutSpecializedSerdeMethod T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "serdetype visit({})", k);
refl::Helper::iterate<T>(
[&](auto type) { static_cast<Derived *>(this)->template visit(type.name, val.*type.getter); });
}
template <typename T>
requires is_variant_v<T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "variant visit({})", k);
visitVariant(val, [&](std::string_view typeName, auto &&v) {
std::string altTypeName = std::string{k} + std::string{typeName};
static_cast<Derived *>(this)->template visit(altTypeName, v);
});
}
template <typename T>
requires is_vector_v<T> || is_set_v<T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "container visit({})", k);
for (auto item : val) {
static_cast<Derived *>(this)->template visit(k, item);
}
}
template <typename T>
requires is_optional_v<T>
void visit(std::string_view k, T &val) {
XLOGF(DBG3, "optional visit({})", k);
using ValueType = typename T::value_type;
if (val.has_value()) {
static_cast<Derived *>(this)->template visit<ValueType>(k, *val);
}
}
};
} // namespace hf3fs::analytics

View File

@@ -0,0 +1,241 @@
#pragma once
#include <arrow/io/file.h>
#include <parquet/stream_writer.h>
#include <type_traits>
#include <utility>
#include <variant>
#include "SerdeObjectVisitor.h"
#include "SerdeSchemaBuilder.h"
#include "common/serde/Serde.h"
#include "common/utils/Nameof.hpp"
#include "common/utils/StrongType.h"
#include "common/utils/UtcTime.h"
namespace hf3fs::analytics {
template <serde::SerdeType SerdeType>
class SerdeObjectWriter : public BaseObjectVisitor<SerdeObjectWriter<SerdeType>> {
public:
SerdeObjectWriter(parquet::StreamWriter &&writer)
: writer_(std::move(writer)),
createTime_(UtcClock::now()) {}
static std::shared_ptr<SerdeObjectWriter> open(const Path path,
const bool append = false,
const size_t maxRowGroupLength = 1'000'000,
const std::vector<parquet::SortingColumn> &sortedColumns = {}) {
// open file
auto openStream = arrow::io::FileOutputStream::Open(path.string(), append);
if (!openStream.ok()) {
XLOGF(ERR, "Failed to open file output stream: {}, error: {}", path.string(), openStream.status().message());
return nullptr;
}
std::shared_ptr<arrow::io::FileOutputStream> outfile;
PARQUET_ASSIGN_OR_THROW(outfile, openStream);
// generate schema
SerdeSchemaBuilder<SerdeType> schemaBuilder;
auto schemaNode = schemaBuilder.getSchema();
if (schemaNode == nullptr) {
return nullptr;
}
parquet::WriterProperties::Builder writerBuilder;
writerBuilder.set_sorting_columns(sortedColumns);
writerBuilder.max_row_group_length(maxRowGroupLength);
writerBuilder.data_page_version(parquet::ParquetDataPageVersion::V2);
// set global encoding and compression method
// writerBuilder.encoding(parquet::Encoding::DELTA_BINARY_PACKED);
writerBuilder.compression(parquet::Compression::ZSTD);
// set encoding for string columns
// for (int fieldIndex = 0; fieldIndex < schemaNode->field_count(); fieldIndex++) {
// auto fieldNode = schemaNode->field(fieldIndex);
// auto fieldType = fieldNode->logical_type();
// if (fieldType->is_string()) writerBuilder.encoding(fieldNode->name(),
// parquet::Encoding::DELTA_LENGTH_BYTE_ARRAY);
// }
try {
auto fileWriter = parquet::ParquetFileWriter::Open(outfile, schemaNode, writerBuilder.build());
if (fileWriter == nullptr) {
XLOGF(ERR, "Failed to open file writer: {}", path.string());
return nullptr;
}
parquet::StreamWriter streamWriter(std::move(fileWriter));
return std::make_shared<SerdeObjectWriter>(std::move(streamWriter));
} catch (const std::exception &ex) {
XLOGF(ERR, "Failed to create stream writer: {}, error: {}", path.string(), ex.what());
return nullptr;
}
}
SerdeObjectWriter &operator<<(const SerdeType &v) {
if (!bool(*this)) return *this;
try {
visit("", v);
writer_ << parquet::EndRow;
} catch (const parquet::ParquetException &ex) {
XLOGF(CRITICAL, "Failed to write to parquet file, error: {}", ex.what());
isOk_ = false;
}
return *this;
}
void endRowGroup() { writer_.EndRowGroup(); }
UtcTime createTime() { return createTime_; }
operator bool() const { return ok(); }
bool ok() const { return isOk_; }
public:
// default
template <typename T>
void visit(std::string_view k, const T &v) = delete;
template <typename T>
requires std::is_arithmetic_v<T>
void visit(std::string_view k, const T &v) {
XLOGF(DBG3, "arithmetic visit({})", k);
writer_ << v;
}
template <typename T>
requires std::is_enum_v<T>
void visit(std::string_view k, const T &v) {
XLOGF(DBG3, "enum visit({})", k);
writer_ << (int32_t)v;
}
template <serde::ConvertibleToString T>
void visit(std::string_view k, const T &v) {
XLOGF(DBG3, "string visit({})", k);
writer_ << v;
}
template <StrongTyped T>
void visit(std::string_view k, const T &v) {
XLOGF(DBG3, "strongtyped visit({})", k);
visit<typename T::UnderlyingType>(k, v.toUnderType());
}
template <serde::WithReadableSerdeMethod T>
void visit(std::string_view k, const T &val) {
auto serialized = serde::SerdeMethod<T>::serdeToReadable(val);
XLOGF(DBG3,
"WithReadableSerdeMethod visit({}), serialized: {} {}",
k,
nameof::nameof_type<decltype(serialized)>(),
serialized);
visit<serde::SerdeToReadableReturnType<T>>(k, serialized);
}
template <serde::WithSerdeMethod T>
void visit(std::string_view k, const T &val) {
auto serialized = serde::SerdeMethod<T>::serdeTo(val);
XLOGF(DBG3,
"WithSerdeMethod visit({}), serialized: {} {}",
k,
nameof::nameof_type<decltype(serialized)>(),
serialized);
visit(k, serialized);
}
template <serde::WithReadableSerdeMemberMethod T>
void visit(std::string_view k, const T &v) {
auto serialized = v.serdeToReadable();
XLOGF(DBG3,
"WithReadableSerdeMemberMethod visit({}), serialized: {} {}",
k,
nameof::nameof_type<decltype(serialized)>(),
serialized);
visit(k, serialized);
}
template <serde::WithSerdeMemberMethod T>
void visit(std::string_view k, const T &v) {
auto serialized = v.serdeTo();
XLOGF(DBG3,
"WithSerdeMemberMethod visit({}), serialized: {} {}",
k,
nameof::nameof_type<decltype(serialized)>(),
serialized);
visit(k, serialized);
}
template <serde::SerdeTypeWithoutSpecializedSerdeMethod T>
void visit(std::string_view k, const T &val) {
XLOGF(DBG3, "serdetype visit({})", k);
BaseObjectVisitor<SerdeObjectWriter>::visit(k, const_cast<T &>(val));
}
template <typename T>
requires is_specialization_of_v<T, folly::Expected>
void visit(std::string_view k, const T &val) {
XLOGF(DBG3, "result visit({})", k);
std::string errorColumnName = std::string{k} + kResultErrorTypeColumnSuffix;
if (val.hasValue()) {
Status ok(StatusCode::kOK);
visit<typename T::error_type>(errorColumnName, ok);
visit<typename T::value_type>(k, val.value());
} else {
typename T::value_type value{};
visit<typename T::error_type>(errorColumnName, val.error());
visit<typename T::value_type>(k, value);
}
}
template <typename T>
requires is_variant_v<T>
void visit(std::string_view k, const T &val) {
XLOGF(DBG3, "variant visit({})", k);
std::string valIdxColumnName = std::string{k} + kVariantValueIndexColumnSuffix;
visit<uint32_t>(valIdxColumnName, val.index());
BaseObjectVisitor<SerdeObjectWriter>::visit(k, const_cast<T &>(val));
}
template <typename T>
requires is_vector_v<T> || is_set_v<T>
void visit(std::string_view k, const T &val) {
XLOGF(DBG3, "container visit({})", k);
auto str = serde::toJsonString(val);
writer_ << str;
}
template <typename T>
requires is_optional_v<T>
void visit(std::string_view k, const T &val) {
XLOGF(DBG3, "optional visit({})", k);
if (!val.has_value()) {
writer_ << "";
} else {
auto str = serde::toJsonString(*val);
writer_ << str;
}
}
private:
parquet::StreamWriter writer_;
UtcTime createTime_;
bool isOk_{true};
};
template <typename T>
SerdeObjectWriter<T> &operator<<(SerdeObjectWriter<T> &writer, parquet::EndRowGroupType) {
writer.endRowGroup();
return writer;
}
} // namespace hf3fs::analytics

View File

@@ -0,0 +1,219 @@
#pragma once
#include <folly/logging/xlog.h>
#include <numeric>
#include <parquet/exception.h>
#include <parquet/schema.h>
#include "SerdeStructVisitor.h"
#include "common/serde/Serde.h"
#include "common/utils/Result.h"
#include "common/utils/TypeTraits.h"
namespace hf3fs::analytics {
template <serde::SerdeType SerdeType>
class SerdeSchemaBuilder : public BaseStructVisitor<SerdeSchemaBuilder<SerdeType>> {
public:
std::shared_ptr<parquet::schema::GroupNode> getSchema() {
try {
fields_.clear();
fieldNameParts_.clear();
this->visit<SerdeType>("");
return std::static_pointer_cast<parquet::schema::GroupNode>(
parquet::schema::GroupNode::Make("schema", parquet::Repetition::REQUIRED, fields_));
} catch (const parquet::ParquetException &ex) {
XLOGF(CRITICAL, "Failed to build schema of type {}, error: {}", nameof::nameof_full_type<SerdeType>(), ex.what());
return nullptr;
}
}
public:
// default
template <typename T>
void visit(std::string_view k) = delete;
template <>
void visit<bool>(std::string_view k) {
XLOGF(DBG3, "bool visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::None(),
parquet::Type::BOOLEAN));
}
template <>
void visit<int16_t>(std::string_view k) {
XLOGF(DBG3, "int16_t visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::Int(16, true),
parquet::Type::INT32));
}
template <>
void visit<uint16_t>(std::string_view k) {
XLOGF(DBG3, "uint16_t visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::Int(16, false),
parquet::Type::INT32));
}
template <>
void visit<int32_t>(std::string_view k) {
XLOGF(DBG3, "int32_t visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::Int(32, true),
parquet::Type::INT32));
}
template <>
void visit<uint32_t>(std::string_view k) {
XLOGF(DBG3, "uint32_t visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::Int(32, false),
parquet::Type::INT32));
}
template <>
void visit<int64_t>(std::string_view k) {
XLOGF(DBG3, "int64_t visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::Int(64, true),
parquet::Type::INT64));
}
template <>
void visit<uint64_t>(std::string_view k) {
XLOGF(DBG3, "uint64_t visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::Int(64, false),
parquet::Type::INT64));
}
template <typename T>
requires std::is_enum_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "enum visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::Int(32, true),
parquet::Type::INT32));
}
template <serde::ConvertibleToString T>
void visit(std::string_view k) {
XLOGF(DBG3, "string visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::String(),
parquet::Type::BYTE_ARRAY));
}
template <StrongTyped T>
void visit(std::string_view k) {
XLOGF(DBG3, "strongtyped visit({}), fullname: '{}'", k, getFieldFullName(k));
BaseStructVisitor<SerdeSchemaBuilder>::template visit<T>(k);
}
template <serde::WithReadableSerdeMethod T>
void visit(std::string_view k) {
XLOGF(DBG3, "WithReadableSerdeMethod visit({})", k);
visit<serde::SerdeToReadableReturnType<T>>(k);
}
template <serde::WithSerdeMethod T>
void visit(std::string_view k) {
XLOGF(DBG3, "WithSerdeMethod visit({})", k);
visit<serde::SerdeToReturnType<T>>(k);
}
template <serde::WithReadableSerdeMemberMethod T>
void visit(std::string_view k) {
XLOGF(DBG3, "WithReadableSerdeMemberMethod visit({})", k);
visit<serde::SerdeToReadableMemberMethodReturnType<T>>(k);
}
template <serde::WithSerdeMemberMethod T>
void visit(std::string_view k) {
XLOGF(DBG3, "WithSerdeMemberMethod visit({})", k);
visit<serde::SerdeToMemberMethodReturnType<T>>(k);
}
template <serde::SerdeTypeWithoutSpecializedSerdeMethod T>
void visit(std::string_view k) {
XLOGF(DBG3, "serdetype visit({}), fullname: '{}'", k, getFieldFullName(k));
if (!k.empty()) fieldNameParts_.push_back(filterOutInvalidChars(k));
BaseStructVisitor<SerdeSchemaBuilder>::template visit<T>(k);
if (!k.empty()) fieldNameParts_.pop_back();
}
template <typename T>
requires is_specialization_of_v<T, folly::Expected>
void visit(std::string_view k) {
XLOGF(DBG3, "result visit({}), fullname: '{}'", k, getFieldFullName(k));
std::string errorColumnName = std::string{k} + kResultErrorTypeColumnSuffix;
visit<typename T::error_type>(errorColumnName);
visit<typename T::value_type>(k);
}
template <typename T>
requires is_variant_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "variant visit({}), fullname: '{}'", k, getFieldFullName(k));
std::string valIdxColumnName = std::string{k} + kVariantValueIndexColumnSuffix;
visit<uint32_t>(valIdxColumnName);
BaseStructVisitor<SerdeSchemaBuilder>::template visit<T>(k);
}
template <typename T>
requires is_vector_v<T> || is_set_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "container visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::String(),
parquet::Type::BYTE_ARRAY));
}
template <typename T>
requires is_optional_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "container visit({}), fullname: '{}'", k, getFieldFullName(k));
fields_.push_back(parquet::schema::PrimitiveNode::Make(getFieldFullName(k),
parquet::Repetition::REQUIRED,
parquet::LogicalType::String(),
parquet::Type::BYTE_ARRAY));
}
private:
std::string getFieldFullName(std::string_view k) {
fieldNameParts_.push_back(filterOutInvalidChars(k));
auto fieldFullName = std::accumulate(fieldNameParts_.begin(),
fieldNameParts_.end(),
std::string{},
[](const std::string &a, const std::string &b) {
return a + (a.empty() ? std::string{} : std::string("_")) + b;
});
fieldNameParts_.pop_back();
return fieldFullName;
}
std::string filterOutInvalidChars(std::string_view k) {
return std::accumulate(k.begin(), k.end(), std::string{}, [](const std::string &a, const char b) {
if (('a' <= b && b <= 'z') || ('A' <= b && b <= 'Z') || ('0' <= b && b <= '9')) return a + b;
return a;
});
};
private:
parquet::schema::NodeVector fields_;
std::vector<std::string> fieldNameParts_;
};
} // namespace hf3fs::analytics

View File

@@ -0,0 +1,123 @@
#pragma once
#include <folly/logging/xlog.h>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include "Common.h"
#include "common/serde/Serde.h"
#include "common/utils/Nameof.hpp"
#include "common/utils/StrongType.h"
#include "common/utils/TypeTraits.h"
namespace hf3fs::analytics {
class StructVisitor {
public:
// default
template <typename T>
void visit(std::string_view) = delete;
template <typename T>
requires std::is_arithmetic_v<T>
void visit(std::string_view) = delete;
template <typename T>
requires std::is_enum_v<T>
void visit(std::string_view) = delete;
template <StrongTyped T>
void visit(std::string_view) = delete;
template <serde::SerdeTypeWithoutSpecializedSerdeMethod T>
void visit(std::string_view) = delete;
template <typename T>
requires is_variant_v<T>
void visit(std::string_view) = delete;
template <typename T>
requires is_vector_v<T> || is_set_v<T>
void visit(std::string_view) = delete;
template <typename T>
requires is_optional_v<T>
void visit(std::string_view) = delete;
};
template <class T, size_t I = 0>
inline void visitVariant(auto &&func) {
using S = std::variant_alternative_t<I, T>;
func(nameof::nameof_short_type<S>(), std::type_identity<S>{});
if constexpr (I + 1 < std::variant_size_v<T>) {
visitVariant<T, I + 1>(func);
}
}
template <typename Derived>
class BaseStructVisitor : public StructVisitor {
public:
// default
template <typename T>
void visit(std::string_view k) = delete;
template <typename T>
requires std::is_arithmetic_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "arithmetic visit({})", k);
static_cast<Derived *>(this)->template visit<T>(k);
}
template <typename T>
requires std::is_enum_v<T>
void visit(std::string_view k) = delete;
template <serde::ConvertibleToString T>
void visit(std::string_view k) = delete;
template <StrongTyped T>
void visit(std::string_view k) {
XLOGF(DBG3, "strongtyped visit({})", k);
static_cast<Derived *>(this)->template visit<typename T::UnderlyingType>(k);
}
template <serde::SerdeTypeWithoutSpecializedSerdeMethod T>
void visit(std::string_view k) {
XLOGF(DBG3, "serdetype visit({})", k);
refl::Helper::iterate<T>([&](auto field) {
using FieldType = std::decay_t<decltype(std::declval<T>().*field.getter)>;
static_cast<Derived *>(this)->template visit<FieldType>(field.name);
});
}
template <typename T>
requires is_variant_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "variant visit({})", k);
visitVariant<T>([&](std::string_view typeName, auto &&v) {
using AlternativeType = typename std::decay_t<decltype(v)>::type;
std::string altTypeName = std::string{k} + std::string{typeName};
static_cast<Derived *>(this)->template visit<AlternativeType>(altTypeName);
});
}
template <typename T>
requires is_vector_v<T> || is_set_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "container visit({})", k);
using ElemValueType = typename T::value_type;
static_cast<Derived *>(this)->template visit<ElemValueType>(k);
}
template <typename T>
requires is_optional_v<T>
void visit(std::string_view k) {
XLOGF(DBG3, "optional visit({})", k);
using ValueType = typename T::value_type;
static_cast<Derived *>(this)->template visit<ValueType>(k);
}
};
} // namespace hf3fs::analytics

View File

@@ -0,0 +1,285 @@
#pragma once
#include <folly/Random.h>
#include <folly/concurrency/UnboundedQueue.h>
#include <future>
#include "SerdeObjectWriter.h"
#include "common/monitor/Recorder.h"
#include "common/monitor/ScopedMetricsWriter.h"
#include "common/utils/ConfigBase.h"
#include "common/utils/Path.h"
#include "common/utils/SysResource.h"
#include "common/utils/UtcTime.h"
namespace hf3fs::analytics {
template <serde::SerdeType SerdeType>
class StructuredTraceLog : public folly::MoveOnly {
struct TraceMeta {
SERDE_STRUCT_FIELD(timestamp, std::time_t{});
SERDE_STRUCT_FIELD(hostname, String{});
};
struct StructuredTrace {
SERDE_STRUCT_FIELD(trace_meta, TraceMeta{});
SERDE_STRUCT_FIELD(_, SerdeType{});
};
using WriterType = SerdeObjectWriter<StructuredTrace>;
using WriterPtr = std::shared_ptr<WriterType>;
public:
class Config : public hf3fs::ConfigBase<Config> {
public:
CONFIG_ITEM(trace_file_dir, Path{"."});
#ifndef NDEBUG
CONFIG_HOT_UPDATED_ITEM(enabled, false);
CONFIG_HOT_UPDATED_ITEM(dump_interval, 60_min);
#else
CONFIG_HOT_UPDATED_ITEM(enabled, true);
CONFIG_HOT_UPDATED_ITEM(dump_interval, 30_s);
#endif
CONFIG_HOT_UPDATED_ITEM(max_num_writers, size_t{1}, ConfigCheckers::checkPositive);
CONFIG_HOT_UPDATED_ITEM(max_row_group_length, size_t{100'000});
};
public:
StructuredTraceLog(const Config &config)
: config_(config),
enabled_(config.enabled()),
typename_(nameof::nameof_short_type<SerdeType>()),
hostname_(SysResource::hostname().value_or("unknown_host")),
latencyTagSet_({{"tag", typename_}, {"instance", fmt::to_string(fmt::ptr(this))}}),
createLatency_("trace_log.create_latency", latencyTagSet_),
appendLatency_("trace_log.append_latency", latencyTagSet_),
flushLatency_("trace_log.flush_latency", latencyTagSet_),
maxNumWriters_(config.max_num_writers()) {
onConfigUpdated_ = config_.addCallbackGuard([this]() {
bool enabled = config_.enabled();
if (enabled_ != enabled) {
enableTraceLog(enabled);
if (!enabled) flush(false /*async*/);
}
if (maxNumWriters_ != config_.max_num_writers()) {
updateMaxNumWriters(config_.max_num_writers());
}
});
uint64_t secsUntilFirstDump =
folly::Random::rand64(config_.dump_interval().asSec().count() / 2, config_.dump_interval().asSec().count());
nextDumpTime_ = microsecondsSinceEpoch(UtcClock::now() + std::chrono::seconds{secsUntilFirstDump});
}
~StructuredTraceLog() { close(); }
bool open() {
auto writer = getOrCreateWriter();
if (!writer) return false;
writerPool_.enqueue(writer);
return true;
}
std::shared_ptr<SerdeType> newEntry(const SerdeType &init = SerdeType{}) {
auto ptr = new SerdeType(init);
return std::shared_ptr<SerdeType>(ptr, [this](SerdeType *ptr) {
this->append(*ptr);
delete ptr;
});
}
void append(const SerdeType &msg) {
if (!enabled_) return;
{
monitor::ScopedLatencyWriter appendLatency(appendLatency_);
StructuredTrace trace{
.trace_meta = TraceMeta{.timestamp = UtcClock::secondsSinceEpoch(), .hostname = hostname_},
._ = msg,
};
WriterPtr writer = getOrCreateWriter();
if (UNLIKELY(writer == nullptr)) {
XLOGF(CRITICAL, "Cannot get a writer of {} trace log in directory {}", typename_, config_.trace_file_dir());
enableTraceLog(false);
return;
}
*writer << trace;
auto writerOk = writer->ok();
writerPool_.enqueue(std::move(writer));
if (UNLIKELY(!writerOk)) enableTraceLog(false);
}
auto currentTime = microsecondsSinceEpoch(UtcClock::now());
if (UNLIKELY(currentTime >= nextDumpTime_)) {
nextDumpTime_ = currentTime + config_.dump_interval().asUs().count();
flush(true /*async*/);
}
}
void flush(bool async, bool shutdown = false) {
auto running = dumpingTrace_.test_and_set();
if (running) return;
monitor::ScopedLatencyWriter flushLatency(flushLatency_);
if (asyncFlush_.valid()) asyncFlush_.wait();
asyncFlush_ = std::async(
std::launch::async,
[this](bool shutdown) {
size_t numWritersToClose = numWriters_.load();
auto now = UtcClock::now();
XLOGF(INFO,
"Flushing {} {} log writers in directory {}",
numWritersToClose,
typename_,
config_.trace_file_dir());
for (size_t i = 0; numWritersToClose > 0; i++) {
// give up flushing old writers after trying for too many loops
if (i >= 10 * maxNumWriters_) {
break;
}
auto writer = writerPool_.dequeue();
if (!writer) continue;
if (writer->createTime() > now) {
writerPool_.enqueue(writer);
continue;
}
if (writer->ok()) {
// add an empty trace at the end of log
*writer << StructuredTrace{
.trace_meta = {.timestamp = UtcClock::secondsSinceEpoch(), .hostname = hostname_}};
}
try {
writer.reset();
} catch (const std::exception &ex) {
XLOGF(ERR,
"Failed to close {} log writer in directory {}, error: {}",
typename_,
config_.trace_file_dir(),
ex.what());
}
if (shutdown)
numWriters_--;
else
writerPool_.enqueue(createNewWriter());
numWritersToClose--;
}
if (numWritersToClose > 0) {
XLOGF(WARN,
"Still have {} {} log writers not closed in directory {}",
numWritersToClose,
typename_,
config_.trace_file_dir());
} else {
XLOGF(INFO, "Flushed {} trace log in directory {}", typename_, config_.trace_file_dir());
}
},
shutdown);
if (!async) asyncFlush_.wait();
dumpingTrace_.clear();
}
void close() {
enableTraceLog(false);
flush(false /*async*/, true /*shutdown*/);
XLOGF(INFO, "Closed {} trace log in directory {}", typename_, config_.trace_file_dir());
}
private:
uint64_t microsecondsSinceEpoch(const UtcTime &time) const {
return std::chrono::duration_cast<std::chrono::microseconds>((time).time_since_epoch()).count();
}
WriterPtr getOrCreateWriter() {
WriterPtr writer;
if (writerPool_.try_dequeue(writer)) return writer;
auto currentNumWriters = numWriters_.load();
if (currentNumWriters < maxNumWriters_) {
bool create = numWriters_.compare_exchange_strong(currentNumWriters, currentNumWriters + 1);
if (create) return createNewWriter();
}
return writerPool_.dequeue();
}
WriterPtr createNewWriter() {
monitor::ScopedLatencyWriter createLatency(createLatency_);
auto timestamp = fmt::localtime(UtcClock::to_time_t(UtcClock::now()));
Path logfilePath =
config_.trace_file_dir() / Path{fmt::format("{:%Y-%m-%d}", timestamp)} / Path{hostname_} /
Path{
fmt::format("{}.{}.{:%Y-%m-%d-%H-%M-%S}.{}.parquet", typename_, hostname_, timestamp, nextLogFileIndex_++)};
if (!boost::filesystem::exists(logfilePath.parent_path())) {
boost::system::error_code err{};
boost::filesystem::create_directories(logfilePath.parent_path(), err);
if (UNLIKELY(err.failed())) {
XLOGF(CRITICAL, "Failed to create directory {}, error: {}", logfilePath.parent_path(), err.message());
return nullptr;
}
}
XLOGF(INFO, "Opening {} trace log: {}", typename_, logfilePath);
return WriterType::open(logfilePath, false /*append*/, config_.max_row_group_length());
}
void enableTraceLog(bool enable) {
enabled_ = enable;
XLOGF(INFO,
"{} {} trace log in directory {}",
enable ? "Enabled" : "Disabled",
typename_,
config_.trace_file_dir());
}
void updateMaxNumWriters(size_t newMaxNumWriters) {
XLOGF(INFO,
"Update max num of writers from {} to {} for {} trace log in directory {}",
maxNumWriters_.load(),
newMaxNumWriters,
typename_,
config_.trace_file_dir());
bool doFlush = maxNumWriters_ > newMaxNumWriters;
maxNumWriters_ = newMaxNumWriters;
if (doFlush) flush(false /*async*/);
}
private:
const Config &config_;
bool enabled_ = false;
const std::string typename_;
const std::string hostname_;
const monitor::TagSet latencyTagSet_;
monitor::LatencyRecorder createLatency_;
monitor::LatencyRecorder appendLatency_;
monitor::LatencyRecorder flushLatency_;
std::unique_ptr<ConfigCallbackGuard> onConfigUpdated_;
std::atomic_size_t maxNumWriters_;
std::atomic_size_t numWriters_ = 0;
std::atomic_size_t nextLogFileIndex_ = 1;
folly::UnboundedQueue<WriterPtr, false, false, true> writerPool_;
std::atomic_uint64_t nextDumpTime_;
std::atomic_flag dumpingTrace_;
std::future<void> asyncFlush_;
};
} // namespace hf3fs::analytics

View File

@@ -0,0 +1,6 @@
add_subdirectory(cli)
add_subdirectory(bin)
add_subdirectory(mgmtd)
add_subdirectory(meta)
add_subdirectory(storage)
add_subdirectory(core)

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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