Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions mooncake-store/include/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,34 @@ class Client {
UUID client_id_;
};

/**
* @brief Fluent builder for configuring a mooncake::Client instance.
*
* Provides readable, type-safe setters with sensible defaults so callers can
* specify only the options they need while reusing existing Client::Create
* logic under the hood.
*/
class MooncakeStoreBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ClientBuilder should be a better name in the context, as we are infact building a Client, not a MooncakeStore.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about MooncakeStoreClientBuilder? Don't be afraid of a long name.

public:
MooncakeStoreBuilder& WithLocalHostname(std::string local_hostname);
MooncakeStoreBuilder& WithMetadataConnectionString(
std::string metadata_connstring);
MooncakeStoreBuilder& WithProtocol(std::string protocol);
MooncakeStoreBuilder& WithProtocolArgs(std::string protocol_args);
MooncakeStoreBuilder& WithMasterEndpoint(std::string master_server_entry);
MooncakeStoreBuilder& WithExistingTransferEngine(
std::shared_ptr<TransferEngine> transfer_engine);

[[nodiscard]] tl::expected<std::shared_ptr<Client>, std::string> Build()
const;

private:
std::optional<std::string> local_hostname_;
std::optional<std::string> metadata_connstring_;
std::string protocol_ = "tcp";
std::optional<std::string> protocol_args_;
std::string master_server_entry_ = kDefaultMasterAddress;
std::shared_ptr<TransferEngine> transfer_engine_ = nullptr;
};

} // namespace mooncake
75 changes: 75 additions & 0 deletions mooncake-store/src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <string_view>
#include <optional>
#include <ranges>
#include <thread>
Expand Down Expand Up @@ -406,6 +407,80 @@ std::optional<std::shared_ptr<Client>> Client::Create(
return client;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithLocalHostname(
std::string local_hostname) {
local_hostname_ = std::move(local_hostname);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use const std::string& instead of std::string for the parameter, and don't move the parameter. Please also update the other "WithXXX" methods that taking strings as arguments.

return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithMetadataConnectionString(
std::string metadata_connstring) {
metadata_connstring_ = std::move(metadata_connstring);
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithProtocol(std::string protocol) {
protocol_ = std::move(protocol);
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithProtocolArgs(
std::string protocol_args) {
// Can add some other engine arguments
protocol_args_ = std::move(protocol_args);
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithMasterEndpoint(
std::string master_server_entry) {
master_server_entry_ = std::move(master_server_entry);
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithExistingTransferEngine(
std::shared_ptr<TransferEngine> transfer_engine) {
transfer_engine_ = std::move(transfer_engine);
return *this;
}

tl::expected<std::shared_ptr<Client>, std::string> MooncakeStoreBuilder::Build()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning string for errors is not a good practice. String is not structured and hard to be programmatically processed. You can see the bad result in the unit tests that you must search the returned string to check whether it is expected.

I suggest to return an ErrorCode for failure. For example, INVALID_PARAMS for missing required field, INTERNAL_ERROR for client creation failure. And provide a GetMissingFields() method to get the missing fields if needed.

const {
std::vector<std::string_view> missing;
if (!local_hostname_) {
missing.emplace_back("local_hostname");
}
if (!metadata_connstring_) {
missing.emplace_back("metadata_connstring");
}

if (!missing.empty()) {
std::string joined;
joined.reserve(missing.size() * 16);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reserve is not necessary because this is not a performance critical path and 16 is a magic number. It's fine to let the string allocate space as needed.

for (size_t i = 0; i < missing.size(); ++i) {
if (i != 0) {
joined.append(", ");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This for loop and the if (i != 0) is redundent and confusing, just remove them.

joined.append(missing[0]);
for (size_t i = 1; i < missing.size(); ++i) {
joined.append(", ");
joined.append(missing[i]);
}
}
auto error_msg =
"MooncakeStoreBuilder missing required fields: " + joined;
LOG(ERROR) << error_msg;
return tl::make_unexpected(std::move(error_msg));
}
auto client =
Client::Create(*local_hostname_, *metadata_connstring_, protocol_,
protocol_args_, master_server_entry_, transfer_engine_);
if (!client) {
std::string error_msg = "Client creation failed";
return tl::make_unexpected(std::move(error_msg));
}
return *client;
}

tl::expected<void, ErrorCode> Client::Get(const std::string& object_key,
std::vector<Slice>& slices) {
auto query_result = Query(object_key);
Expand Down
1 change: 0 additions & 1 deletion mooncake-store/src/pybind_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ tl::expected<void, ErrorCode> PyClient::setup_internal(
std::optional<std::string> device_name =
(rdma_devices.empty() ? std::nullopt
: std::make_optional(rdma_devices));

auto client_opt = mooncake::Client::Create(
this->local_hostname, metadata_server, protocol, device_name,
master_server_addr, transfer_engine);
Expand Down
62 changes: 62 additions & 0 deletions mooncake-store/tests/client_integration_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,68 @@ DEFINE_uint64(default_kv_lease_ttl, mooncake::DEFAULT_DEFAULT_KV_LEASE_TTL,
namespace mooncake {
namespace testing {

class MooncakeStoreBuilderTest : public ::testing::Test {
protected:
void SetUp() override {
google::InitGoogleLogging("MooncakeStoreBuilderTest");
FLAGS_logtostderr = true;
}

void TearDown() override { google::ShutdownGoogleLogging(); }
};

TEST_F(MooncakeStoreBuilderTest, MissingAllRequiredFields) {
MooncakeStoreBuilder builder;

auto result = builder.Build();

ASSERT_FALSE(result.has_value());
const std::string& error = result.error();
EXPECT_NE(error.find("local_hostname"), std::string::npos);
EXPECT_NE(error.find("metadata_connstring"), std::string::npos);
}

TEST_F(MooncakeStoreBuilderTest, MissingLocalHostnameOnly) {
MooncakeStoreBuilder builder;
builder.WithMetadataConnectionString("metastore:1234");

auto result = builder.Build();

ASSERT_FALSE(result.has_value());
const std::string& error = result.error();
EXPECT_NE(error.find("local_hostname"), std::string::npos);
EXPECT_EQ(error.find("metadata_connstring"), std::string::npos);
}

TEST_F(MooncakeStoreBuilderTest, MissingMetadataConnectionStringOnly) {
MooncakeStoreBuilder builder;
builder.WithLocalHostname("localhost:1234");

auto result = builder.Build();

ASSERT_FALSE(result.has_value());
const std::string& error = result.error();
EXPECT_NE(error.find("metadata_connstring"), std::string::npos);
EXPECT_EQ(error.find("local_hostname"), std::string::npos);
}

TEST_F(MooncakeStoreBuilderTest, ProvidedAllRequiredFields) {
MooncakeStoreBuilder builder;
builder.WithLocalHostname("localhost:1234");
builder.WithMetadataConnectionString("metadata:5678");

auto result = builder.Build();

if (result.has_value()) {
SUCCEED();
} else {
const std::string& error = result.error();
EXPECT_EQ(error.find("missing required fields"), std::string::npos);
EXPECT_EQ(error.find("local_hostname"), std::string::npos);
EXPECT_EQ(error.find("metadata_connstring"), std::string::npos);
}
}

class ClientIntegrationTest : public ::testing::Test {
protected:
static std::shared_ptr<Client> CreateClient(const std::string& host_name) {
Expand Down
Loading