Compare commits
3 Commits
ignore_que
...
distribute
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
742f3a2e4c | ||
|
|
7d9352beb5 | ||
|
|
97b0a56b3e |
@@ -20,18 +20,14 @@ if [ ! -f "$INPUT" ]; then
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} BEGIN and COMMIT are required because variables share the same name (e.g. row)"
|
||||
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} CONSTRAINTS are just skipped -> ${COLOR_RED}please create constraints manually if needed${COLOR_NULL}"
|
||||
|
||||
echo 'CREATE INDEX ON :`UNIQUE IMPORT LABEL`(`UNIQUE IMPORT ID`);' > "$OUTPUT"
|
||||
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} CONSTRAINTS are just skipped -> ${COLOR_RED}please create consraints manually if needed${COLOR_NULL}"
|
||||
|
||||
sed -e 's/^:begin/BEGIN/g; s/^BEGIN$/BEGIN;/g;' \
|
||||
-e 's/^:commit/COMMIT/g; s/^COMMIT$/COMMIT;/g;' \
|
||||
-e '/^CALL/d; /^SCHEMA AWAIT/d;' \
|
||||
-e 's/CREATE RANGE INDEX FOR (n:/CREATE INDEX ON :/g;' \
|
||||
-e 's/) ON (n./(/g;' \
|
||||
-e '/^CREATE CONSTRAINT/d; /^DROP CONSTRAINT/d;' "$INPUT" >> "$OUTPUT"
|
||||
|
||||
echo 'DROP INDEX ON :`UNIQUE IMPORT LABEL`(`UNIQUE IMPORT ID`);' >> "$OUTPUT"
|
||||
-e '/^CREATE CONSTRAINT/d; /^DROP CONSTRAINT/d;' "$INPUT" > "$OUTPUT"
|
||||
|
||||
echo ""
|
||||
echo -e "${COLOR_GREEN}DONE!${COLOR_NULL} Please find Memgraph compatible cypherl|.cypher file under $OUTPUT"
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
COLOR_ORANGE="\e[38;5;208m"
|
||||
COLOR_GREEN="\e[38;5;35m"
|
||||
COLOR_RED="\e[0;31m"
|
||||
COLOR_NULL="\e[0m"
|
||||
|
||||
print_help() {
|
||||
echo -e "${COLOR_ORANGE}HOW TO RUN:${COLOR_NULL} $0 input_file_schema_path input_file_nodes_path input_file_relationships_path input_file_cleanup_path output_file_path"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -ne 5 ]; then
|
||||
print_help
|
||||
fi
|
||||
INPUT_SCHEMA="$1"
|
||||
INPUT_NODES="$2"
|
||||
INPUT_RELATIONSHIPS="$3"
|
||||
INPUT_CLEANUP="$4"
|
||||
OUTPUT="$5"
|
||||
|
||||
if [ ! -f "$INPUT_SCHEMA" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT_NODES" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT_RELATIONSHIPS" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT_CLEANUP" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} BEGIN and COMMIT are required because variables share the same name (e.g. row)"
|
||||
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} CONSTRAINTS are just skipped -> ${COLOR_RED}please create constraints manually if needed${COLOR_NULL}"
|
||||
|
||||
|
||||
echo 'CREATE INDEX ON :`UNIQUE IMPORT LABEL`(`UNIQUE IMPORT ID`);' > "$OUTPUT"
|
||||
|
||||
sed -e 's/CREATE RANGE INDEX FOR (n:/CREATE INDEX ON :/g;' \
|
||||
-e 's/) ON (n./(/g;' \
|
||||
-e '/^CREATE CONSTRAINT/d' $INPUT_SCHEMA >> "$OUTPUT"
|
||||
|
||||
cat "$INPUT_NODES" >> "$OUTPUT"
|
||||
cat "$INPUT_RELATIONSHIPS" >> "$OUTPUT"
|
||||
|
||||
sed -e '/^DROP CONSTRAINT/d' "$INPUT_CLEANUP" >> "$OUTPUT"
|
||||
|
||||
echo 'DROP INDEX ON :`UNIQUE IMPORT LABEL`(`UNIQUE IMPORT ID`);' >> "$OUTPUT"
|
||||
|
||||
echo ""
|
||||
echo -e "${COLOR_GREEN}DONE!${COLOR_NULL} Please find Memgraph compatible cypherl|.cypher file under $OUTPUT"
|
||||
echo ""
|
||||
echo "Please import data by executing => \`cat $OUTPUT | mgconsole\`"
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
COLOR_ORANGE="\e[38;5;208m"
|
||||
COLOR_GREEN="\e[38;5;35m"
|
||||
COLOR_RED="\e[0;31m"
|
||||
COLOR_NULL="\e[0m"
|
||||
|
||||
print_help() {
|
||||
echo -e "${COLOR_ORANGE}HOW TO RUN:${COLOR_NULL} $0 input_file_schema_path input_file_nodes_path input_file_relationships_path input_file_cleanup_path output_file_schema_path output_file_nodes_path output_file_relationships_path output_file_cleanup_path"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -ne 8 ]; then
|
||||
print_help
|
||||
fi
|
||||
INPUT_SCHEMA="$1"
|
||||
INPUT_NODES="$2"
|
||||
INPUT_RELATIONSHIPS="$3"
|
||||
INPUT_CLEANUP="$4"
|
||||
OUTPUT_SCHEMA="$5"
|
||||
OUTPUT_NODES="$6"
|
||||
OUTPUT_RELATIONSHIPS="$7"
|
||||
OUTPUT_CLEANUP="$8"
|
||||
|
||||
if [ ! -f "$INPUT_SCHEMA" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT_NODES" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT_RELATIONSHIPS" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT_CLEANUP" ]; then
|
||||
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
|
||||
print_help
|
||||
fi
|
||||
|
||||
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} BEGIN and COMMIT are required because variables share the same name (e.g. row)"
|
||||
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} CONSTRAINTS are just skipped -> ${COLOR_RED}please create constraints manually if needed${COLOR_NULL}"
|
||||
|
||||
|
||||
echo 'CREATE INDEX ON :`UNIQUE IMPORT LABEL`(`UNIQUE IMPORT ID`);' > "$OUTPUT_SCHEMA"
|
||||
|
||||
sed -e 's/CREATE RANGE INDEX FOR (n:/CREATE INDEX ON :/g;' \
|
||||
-e 's/) ON (n./(/g;' \
|
||||
-e '/^CREATE CONSTRAINT/d' $INPUT_SCHEMA >> "$OUTPUT_SCHEMA"
|
||||
|
||||
cat "$INPUT_NODES" > "$OUTPUT_NODES"
|
||||
cat "$INPUT_RELATIONSHIPS" > "$OUTPUT_RELATIONSHIPS"
|
||||
|
||||
sed -e '/^DROP CONSTRAINT/d' "$INPUT_CLEANUP" >> "$OUTPUT_CLEANUP"
|
||||
|
||||
echo 'DROP INDEX ON :`UNIQUE IMPORT LABEL`(`UNIQUE IMPORT ID`);' >> "$OUTPUT_CLEANUP"
|
||||
|
||||
echo ""
|
||||
echo -e "${COLOR_GREEN}DONE!${COLOR_NULL} Please find Memgraph compatible cypherl|.cypher files under $OUTPUT_SCHEMA, $OUTPUT_NODES, $OUTPUT_RELATIONSHIPS and $OUTPUT_CLEANUP"
|
||||
echo ""
|
||||
echo "Please import data by executing => \`cat $OUTPUT_SCHEMA | mgconsole\`, \`cat $OUTPUT_NODES | mgconsole\`, \`cat $OUTPUT_RELATIONSHIPS | mgconsole\` and \`cat $OUTPUT_CLEANUP | mgconsole\`"
|
||||
@@ -23,7 +23,6 @@ target_sources(mg-coordination
|
||||
include/nuraft/coordinator_state_manager.hpp
|
||||
|
||||
PRIVATE
|
||||
coordinator_config.cpp
|
||||
coordinator_client.cpp
|
||||
coordinator_state.cpp
|
||||
coordinator_rpc.cpp
|
||||
|
||||
@@ -135,7 +135,8 @@ auto CoordinatorClient::SendSwapMainUUIDRpc(utils::UUID const &uuid) const -> bo
|
||||
|
||||
auto CoordinatorClient::SendUnregisterReplicaRpc(std::string_view instance_name) const -> bool {
|
||||
try {
|
||||
auto stream{rpc_client_.Stream<UnregisterReplicaRpc>(instance_name)};
|
||||
auto stream{rpc_client_.Stream<UnregisterReplicaRpc>(
|
||||
std::string(instance_name))}; // TODO: (andi) Try to change to stream string_view and do just one copy later
|
||||
if (!stream.AwaitResponse().success) {
|
||||
spdlog::error("Failed to receive successful RPC response for unregistering replica!");
|
||||
return false;
|
||||
|
||||
@@ -18,87 +18,78 @@
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
void to_json(nlohmann::json &j, InstanceState const &instance_state) {
|
||||
j = nlohmann::json{{"config", instance_state.config}, {"status", instance_state.status}};
|
||||
}
|
||||
using replication_coordination_glue::ReplicationRole;
|
||||
|
||||
void from_json(nlohmann::json const &j, InstanceState &instance_state) {
|
||||
j.at("config").get_to(instance_state.config);
|
||||
j.at("status").get_to(instance_state.status);
|
||||
}
|
||||
|
||||
CoordinatorClusterState::CoordinatorClusterState(std::map<std::string, InstanceState, std::less<>> instances)
|
||||
: instances_{std::move(instances)} {}
|
||||
|
||||
CoordinatorClusterState::CoordinatorClusterState(CoordinatorClusterState const &other) : instances_{other.instances_} {}
|
||||
CoordinatorClusterState::CoordinatorClusterState(CoordinatorClusterState const &other)
|
||||
: instance_roles_{other.instance_roles_} {}
|
||||
|
||||
CoordinatorClusterState &CoordinatorClusterState::operator=(CoordinatorClusterState const &other) {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
instances_ = other.instances_;
|
||||
instance_roles_ = other.instance_roles_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CoordinatorClusterState::CoordinatorClusterState(CoordinatorClusterState &&other) noexcept
|
||||
: instances_{std::move(other.instances_)} {}
|
||||
: instance_roles_{std::move(other.instance_roles_)} {}
|
||||
|
||||
CoordinatorClusterState &CoordinatorClusterState::operator=(CoordinatorClusterState &&other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
instances_ = std::move(other.instances_);
|
||||
instance_roles_ = std::move(other.instance_roles_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::MainExists() const -> bool {
|
||||
auto lock = std::shared_lock{log_lock_};
|
||||
return std::ranges::any_of(instances_,
|
||||
[](auto const &entry) { return entry.second.status == ReplicationRole::MAIN; });
|
||||
return std::ranges::any_of(instance_roles_,
|
||||
[](auto const &entry) { return entry.second.role == ReplicationRole::MAIN; });
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::IsMain(std::string_view instance_name) const -> bool {
|
||||
auto lock = std::shared_lock{log_lock_};
|
||||
auto const it = instances_.find(instance_name);
|
||||
return it != instances_.end() && it->second.status == ReplicationRole::MAIN;
|
||||
auto const it = instance_roles_.find(instance_name);
|
||||
return it != instance_roles_.end() && it->second.role == ReplicationRole::MAIN;
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::IsReplica(std::string_view instance_name) const -> bool {
|
||||
auto lock = std::shared_lock{log_lock_};
|
||||
auto const it = instances_.find(instance_name);
|
||||
return it != instances_.end() && it->second.status == ReplicationRole::REPLICA;
|
||||
auto const it = instance_roles_.find(instance_name);
|
||||
return it != instance_roles_.end() && it->second.role == ReplicationRole::REPLICA;
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::InsertInstance(std::string instance_name, InstanceState instance_state) -> void {
|
||||
auto lock = std::lock_guard{log_lock_};
|
||||
instances_.insert_or_assign(std::move(instance_name), std::move(instance_state));
|
||||
auto CoordinatorClusterState::InsertInstance(std::string_view instance_name, ReplicationRole role) -> void {
|
||||
auto lock = std::unique_lock{log_lock_};
|
||||
instance_roles_[instance_name.data()].role = role;
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::DoAction(TRaftLog log_entry, RaftLogAction log_action) -> void {
|
||||
auto lock = std::lock_guard{log_lock_};
|
||||
auto lock = std::unique_lock{log_lock_};
|
||||
switch (log_action) {
|
||||
case RaftLogAction::REGISTER_REPLICATION_INSTANCE: {
|
||||
auto const &config = std::get<CoordinatorClientConfig>(log_entry);
|
||||
instances_[config.instance_name] = InstanceState{config, ReplicationRole::REPLICA};
|
||||
instance_roles_[config.instance_name] = InstanceState{config, ReplicationRole::REPLICA};
|
||||
break;
|
||||
}
|
||||
case RaftLogAction::UNREGISTER_REPLICATION_INSTANCE: {
|
||||
auto const instance_name = std::get<std::string>(log_entry);
|
||||
instances_.erase(instance_name);
|
||||
instance_roles_.erase(instance_name);
|
||||
break;
|
||||
}
|
||||
case RaftLogAction::SET_INSTANCE_AS_MAIN: {
|
||||
auto const instance_name = std::get<std::string>(log_entry);
|
||||
auto it = instances_.find(instance_name);
|
||||
MG_ASSERT(it != instances_.end(), "Instance does not exist as part of raft state!");
|
||||
it->second.status = ReplicationRole::MAIN;
|
||||
auto it = instance_roles_.find(instance_name);
|
||||
MG_ASSERT(it != instance_roles_.end(), "Instance does not exist as part of raft state!");
|
||||
it->second.role = ReplicationRole::MAIN;
|
||||
break;
|
||||
}
|
||||
case RaftLogAction::SET_INSTANCE_AS_REPLICA: {
|
||||
auto const instance_name = std::get<std::string>(log_entry);
|
||||
auto it = instances_.find(instance_name);
|
||||
MG_ASSERT(it != instances_.end(), "Instance does not exist as part of raft state!");
|
||||
it->second.status = ReplicationRole::REPLICA;
|
||||
auto it = instance_roles_.find(instance_name);
|
||||
MG_ASSERT(it != instance_roles_.end(), "Instance does not exist as part of raft state!");
|
||||
it->second.role = ReplicationRole::REPLICA;
|
||||
break;
|
||||
}
|
||||
case RaftLogAction::UPDATE_UUID: {
|
||||
@@ -108,37 +99,64 @@ auto CoordinatorClusterState::DoAction(TRaftLog log_entry, RaftLogAction log_act
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: (andi) Improve based on Gareth's comments
|
||||
auto CoordinatorClusterState::Serialize(ptr<buffer> &data) -> void {
|
||||
auto lock = std::shared_lock{log_lock_};
|
||||
auto const role_to_string = [](auto const &role) -> std::string_view {
|
||||
switch (role) {
|
||||
case ReplicationRole::MAIN:
|
||||
return "main";
|
||||
case ReplicationRole::REPLICA:
|
||||
return "replica";
|
||||
}
|
||||
};
|
||||
|
||||
// .at(0) is hack to solve the problem with json serialization of map
|
||||
auto const log = nlohmann::json{instances_}.at(0).dump();
|
||||
auto const entry_to_string = [&role_to_string](auto const &entry) {
|
||||
return fmt::format("{}_{}", entry.first, role_to_string(entry.second.role));
|
||||
};
|
||||
|
||||
data = buffer::alloc(sizeof(uint32_t) + log.size());
|
||||
auto instances_str_view = instance_roles_ | ranges::views::transform(entry_to_string);
|
||||
uint32_t size =
|
||||
std::accumulate(instances_str_view.begin(), instances_str_view.end(), 0,
|
||||
[](uint32_t acc, auto const &entry) { return acc + sizeof(uint32_t) + entry.size(); });
|
||||
|
||||
data = buffer::alloc(size);
|
||||
buffer_serializer bs(data);
|
||||
bs.put_str(log);
|
||||
std::for_each(instances_str_view.begin(), instances_str_view.end(), [&bs](auto const &entry) { bs.put_str(entry); });
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::Deserialize(buffer &data) -> CoordinatorClusterState {
|
||||
buffer_serializer bs(data);
|
||||
auto const j = nlohmann::json::parse(bs.get_str());
|
||||
auto instances = j.get<std::map<std::string, InstanceState, std::less<>>>();
|
||||
auto const str_to_role = [](auto const &str) -> ReplicationRole {
|
||||
if (str == "main") {
|
||||
return ReplicationRole::MAIN;
|
||||
}
|
||||
return ReplicationRole::REPLICA;
|
||||
};
|
||||
|
||||
return CoordinatorClusterState{std::move(instances)};
|
||||
CoordinatorClusterState cluster_state;
|
||||
buffer_serializer bs(data);
|
||||
while (bs.size() > 0) {
|
||||
auto const entry = bs.get_str();
|
||||
auto const first_dash = entry.find('_');
|
||||
auto const instance_name = entry.substr(0, first_dash);
|
||||
auto const role_str = entry.substr(first_dash + 1);
|
||||
cluster_state.InsertInstance(instance_name, str_to_role(role_str));
|
||||
}
|
||||
return cluster_state;
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::GetInstances() const -> std::vector<InstanceState> {
|
||||
auto lock = std::shared_lock{log_lock_};
|
||||
return instances_ | ranges::views::values | ranges::to<std::vector<InstanceState>>;
|
||||
return instance_roles_ | ranges::views::values | ranges::to<std::vector<InstanceState>>;
|
||||
}
|
||||
|
||||
auto CoordinatorClusterState::GetUUID() const -> utils::UUID { return uuid_; }
|
||||
|
||||
auto CoordinatorClusterState::FindCurrentMainInstanceName() const -> std::optional<std::string> {
|
||||
auto lock = std::shared_lock{log_lock_};
|
||||
auto const it =
|
||||
std::ranges::find_if(instances_, [](auto const &entry) { return entry.second.status == ReplicationRole::MAIN; });
|
||||
if (it == instances_.end()) {
|
||||
auto const it = std::ranges::find_if(instance_roles_,
|
||||
[](auto const &entry) { return entry.second.role == ReplicationRole::MAIN; });
|
||||
if (it == instance_roles_.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->first;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
void to_json(nlohmann::json &j, ReplClientInfo const &config) {
|
||||
j = nlohmann::json{{"instance_name", config.instance_name},
|
||||
{"replication_mode", config.replication_mode},
|
||||
{"replication_ip_address", config.replication_ip_address},
|
||||
{"replication_port", config.replication_port}};
|
||||
}
|
||||
|
||||
void from_json(nlohmann::json const &j, ReplClientInfo &config) {
|
||||
config.instance_name = j.at("instance_name").get<std::string>();
|
||||
config.replication_mode = j.at("replication_mode").get<replication_coordination_glue::ReplicationMode>();
|
||||
config.replication_ip_address = j.at("replication_ip_address").get<std::string>();
|
||||
config.replication_port = j.at("replication_port").get<uint16_t>();
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, CoordinatorClientConfig const &config) {
|
||||
j = nlohmann::json{{"instance_name", config.instance_name},
|
||||
{"ip_address", config.ip_address},
|
||||
{"port", config.port},
|
||||
{"instance_health_check_frequency_sec", config.instance_health_check_frequency_sec.count()},
|
||||
{"instance_down_timeout_sec", config.instance_down_timeout_sec.count()},
|
||||
{"instance_get_uuid_frequency_sec", config.instance_get_uuid_frequency_sec.count()},
|
||||
{"replication_client_info", config.replication_client_info}};
|
||||
}
|
||||
|
||||
void from_json(nlohmann::json const &j, CoordinatorClientConfig &config) {
|
||||
config.instance_name = j.at("instance_name").get<std::string>();
|
||||
config.ip_address = j.at("ip_address").get<std::string>();
|
||||
config.port = j.at("port").get<uint16_t>();
|
||||
config.instance_health_check_frequency_sec =
|
||||
std::chrono::seconds{j.at("instance_health_check_frequency_sec").get<int>()};
|
||||
config.instance_down_timeout_sec = std::chrono::seconds{j.at("instance_down_timeout_sec").get<int>()};
|
||||
config.instance_get_uuid_frequency_sec = std::chrono::seconds{j.at("instance_get_uuid_frequency_sec").get<int>()};
|
||||
config.replication_client_info = j.at("replication_client_info").get<ReplClientInfo>();
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -140,7 +140,7 @@ void CoordinatorHandlers::PromoteReplicaToMainHandler(replication::ReplicationHa
|
||||
.port = repl_info_config.replication_port,
|
||||
};
|
||||
};
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1000});
|
||||
// registering replicas
|
||||
for (auto const &config : req.replication_clients_info | ranges::views::transform(converter)) {
|
||||
auto instance_client = replication_handler.RegisterReplica(config);
|
||||
|
||||
@@ -36,7 +36,7 @@ CoordinatorInstance::CoordinatorInstance()
|
||||
spdlog::info("Leader changed, starting all replication instances!");
|
||||
auto const instances = raft_state_.GetInstances();
|
||||
auto replicas = instances | ranges::views::filter([](auto const &instance) {
|
||||
return instance.status == ReplicationRole::REPLICA;
|
||||
return instance.role == ReplicationRole::REPLICA;
|
||||
});
|
||||
|
||||
std::ranges::for_each(replicas, [this](auto &replica) {
|
||||
@@ -47,7 +47,10 @@ CoordinatorInstance::CoordinatorInstance()
|
||||
});
|
||||
|
||||
auto main = instances | ranges::views::filter(
|
||||
[](auto const &instance) { return instance.status == ReplicationRole::MAIN; });
|
||||
[](auto const &instance) { return instance.role == ReplicationRole::MAIN; });
|
||||
|
||||
// TODO: (andi) Add support for this
|
||||
// MG_ASSERT(std::ranges::distance(main) == 1, "There should be exactly one main instance");
|
||||
|
||||
std::ranges::for_each(main, [this](auto &main_instance) {
|
||||
spdlog::info("Starting main instance {}", main_instance.config.instance_name);
|
||||
@@ -57,21 +60,39 @@ CoordinatorInstance::CoordinatorInstance()
|
||||
});
|
||||
|
||||
std::ranges::for_each(repl_instances_, [this](auto &instance) {
|
||||
instance.SetNewMainUUID(raft_state_.GetUUID());
|
||||
instance.StartFrequentCheck();
|
||||
instance.SetNewMainUUID(raft_state_.GetUUID()); // TODO: (andi) Rename
|
||||
instance.ResumeFrequentCheck();
|
||||
});
|
||||
is_running_.store(true, std::memory_order::acquire);
|
||||
},
|
||||
[this]() {
|
||||
spdlog::info("Leader changed, stopping all replication instances!");
|
||||
spdlog::info("Leader changed, trying to stop all replication instances, thread id {}!",
|
||||
std::this_thread::get_id());
|
||||
/// TODO Add to pool
|
||||
// auto lock = std::lock_guard{coord_instance_lock_}; // RAII
|
||||
// std::ranges::for_each(repl_instances_)
|
||||
repl_instances_.clear();
|
||||
spdlog::info("Leader changed, stopped all replication instances!");
|
||||
return;
|
||||
is_running_.store(false, std::memory_order::acquire);
|
||||
pool_.AddTask([this]() {
|
||||
auto lock = std::lock_guard{coord_instance_lock_};
|
||||
std::ranges::for_each(repl_instances_, [](auto &instance) { instance.PauseFrequentCheck(); });
|
||||
});
|
||||
})) {
|
||||
client_succ_cb_ = [](CoordinatorInstance *self, std::string_view repl_instance_name) -> void {
|
||||
auto lock = std::lock_guard{self->coord_instance_lock_};
|
||||
if (!self->is_running_) {
|
||||
return;
|
||||
}
|
||||
auto lock = std::lock_guard{self->coord_instance_lock_}; // RAII
|
||||
auto &repl_instance = self->FindReplicationInstance(repl_instance_name);
|
||||
std::invoke(repl_instance.GetSuccessCallback(), self, repl_instance_name);
|
||||
};
|
||||
|
||||
client_fail_cb_ = [](CoordinatorInstance *self, std::string_view repl_instance_name) -> void {
|
||||
if (!self->is_running_) {
|
||||
return;
|
||||
}
|
||||
auto lock = std::lock_guard{self->coord_instance_lock_};
|
||||
auto &repl_instance = self->FindReplicationInstance(repl_instance_name);
|
||||
std::invoke(repl_instance.GetFailCallback(), self, repl_instance_name);
|
||||
@@ -95,8 +116,10 @@ auto CoordinatorInstance::ShowInstances() const -> std::vector<InstanceStatus> {
|
||||
.raft_socket_address = instance->get_endpoint(),
|
||||
.cluster_role = "coordinator",
|
||||
.health = "unknown"}; // TODO: (andi) Get this info from RAFT and test it or when we will move
|
||||
// CoordinatorState to every instance, we can be smarter about this using our RPC.
|
||||
};
|
||||
auto instances_status = utils::fmap(raft_state_.GetAllCoordinators(), coord_instance_to_status);
|
||||
|
||||
auto instances_status = utils::fmap(coord_instance_to_status, raft_state_.GetAllCoordinators());
|
||||
|
||||
if (raft_state_.IsLeader()) {
|
||||
auto const stringify_repl_role = [this](ReplicationInstance const &instance) -> std::string {
|
||||
@@ -122,14 +145,14 @@ auto CoordinatorInstance::ShowInstances() const -> std::vector<InstanceStatus> {
|
||||
std::ranges::transform(repl_instances_, std::back_inserter(instances_status), process_repl_instance_as_leader);
|
||||
}
|
||||
} else {
|
||||
auto const stringify_inst_status = [](ReplicationRole status) -> std::string {
|
||||
return status == ReplicationRole::MAIN ? "main" : "replica";
|
||||
auto const stringify_repl_role = [](ReplicationRole role) -> std::string {
|
||||
return role == ReplicationRole::MAIN ? "main" : "replica";
|
||||
};
|
||||
|
||||
// TODO: (andi) Add capability that followers can also return socket addresses
|
||||
auto process_repl_instance_as_follower = [&stringify_inst_status](auto const &instance) -> InstanceStatus {
|
||||
auto process_repl_instance_as_follower = [&stringify_repl_role](auto const &instance) -> InstanceStatus {
|
||||
return {.instance_name = instance.config.instance_name,
|
||||
.cluster_role = stringify_inst_status(instance.status),
|
||||
.cluster_role = stringify_repl_role(instance.role),
|
||||
.health = "unknown"};
|
||||
};
|
||||
|
||||
@@ -141,6 +164,9 @@ auto CoordinatorInstance::ShowInstances() const -> std::vector<InstanceStatus> {
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::TryFailover() -> void {
|
||||
if (!is_running_) {
|
||||
return;
|
||||
}
|
||||
auto const is_replica = [this](ReplicationInstance const &instance) { return IsReplica(instance.InstanceName()); };
|
||||
|
||||
auto alive_replicas =
|
||||
@@ -211,22 +237,40 @@ auto CoordinatorInstance::TryFailover() -> void {
|
||||
spdlog::warn("Failover failed since promoting replica to main failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::info("Promote to main done, trying to append update uuid, thread id {}, is leader {}",
|
||||
std::this_thread::get_id(), raft_state_.IsLeader());
|
||||
// TODO (antoniofilipovic) : This can fail and we don't know we change uuid
|
||||
if (!raft_state_.AppendUpdateUUIDLog(new_main_uuid)) {
|
||||
spdlog::trace("Append entry update uuid failed, thread id {}, is leader {}", std::this_thread::get_id(),
|
||||
raft_state_.IsLeader());
|
||||
return;
|
||||
}
|
||||
|
||||
auto const new_main_instance_name = new_main->InstanceName();
|
||||
|
||||
spdlog::info("Promote to main done, trying to set instance as main log");
|
||||
// TODO (antoniofilipovic): This can fail and we don't know we have set up new MAIN
|
||||
if (!raft_state_.AppendSetInstanceAsMainLog(new_main_instance_name)) {
|
||||
spdlog::trace("Append set instance as main failed, thread id {}, is leader {}", std::this_thread::get_id(),
|
||||
raft_state_.IsLeader());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Set trying failover on instance (chosen instance)
|
||||
// If all passes, all good
|
||||
// if it fails, we need to check if main was promoted and we have correct new uuid
|
||||
// PromoteToMain -> split on promote to main and enable writting -> solves issue that we have
|
||||
// consistent state
|
||||
|
||||
spdlog::info("Failover successful! Instance {} promoted to main.", new_main->InstanceName());
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::SetReplicationInstanceToMain(std::string_view instance_name)
|
||||
-> SetInstanceToMainCoordinatorStatus {
|
||||
if (!is_running_) {
|
||||
return SetInstanceToMainCoordinatorStatus::NOT_LEADER;
|
||||
;
|
||||
}
|
||||
auto lock = std::lock_guard{coord_instance_lock_};
|
||||
|
||||
if (raft_state_.MainExists()) {
|
||||
@@ -290,6 +334,10 @@ auto CoordinatorInstance::SetReplicationInstanceToMain(std::string_view instance
|
||||
|
||||
auto CoordinatorInstance::RegisterReplicationInstance(CoordinatorClientConfig const &config)
|
||||
-> RegisterInstanceCoordinatorStatus {
|
||||
if (!is_running_) {
|
||||
return RegisterInstanceCoordinatorStatus::NOT_LEADER;
|
||||
;
|
||||
}
|
||||
auto lock = std::lock_guard{coord_instance_lock_};
|
||||
|
||||
if (std::ranges::any_of(repl_instances_, [instance_name = config.instance_name](ReplicationInstance const &instance) {
|
||||
@@ -336,6 +384,10 @@ auto CoordinatorInstance::RegisterReplicationInstance(CoordinatorClientConfig co
|
||||
|
||||
auto CoordinatorInstance::UnregisterReplicationInstance(std::string_view instance_name)
|
||||
-> UnregisterInstanceCoordinatorStatus {
|
||||
if (!is_running_) {
|
||||
return UnregisterInstanceCoordinatorStatus::NOT_LEADER;
|
||||
;
|
||||
}
|
||||
auto lock = std::lock_guard{coord_instance_lock_};
|
||||
|
||||
if (!raft_state_.RequestLeadership()) {
|
||||
@@ -351,11 +403,11 @@ auto CoordinatorInstance::UnregisterReplicationInstance(std::string_view instanc
|
||||
return UnregisterInstanceCoordinatorStatus::NO_INSTANCE_WITH_NAME;
|
||||
}
|
||||
|
||||
auto const is_main = [this](ReplicationInstance const &instance) {
|
||||
return IsMain(instance.InstanceName()) && instance.GetMainUUID() == raft_state_.GetUUID() && instance.IsAlive();
|
||||
};
|
||||
// TODO: (andi) Change so that RaftLogState is the central place for asking who is main...
|
||||
|
||||
if (is_main(*inst_to_remove)) {
|
||||
auto const is_main = [this](ReplicationInstance const &instance) { return IsMain(instance.InstanceName()); };
|
||||
|
||||
if (is_main(*inst_to_remove) && inst_to_remove->IsAlive()) {
|
||||
return UnregisterInstanceCoordinatorStatus::IS_MAIN;
|
||||
}
|
||||
|
||||
@@ -385,6 +437,9 @@ auto CoordinatorInstance::AddCoordinatorInstance(uint32_t raft_server_id, uint32
|
||||
}
|
||||
|
||||
void CoordinatorInstance::MainFailCallback(std::string_view repl_instance_name) {
|
||||
if (!is_running_) {
|
||||
return;
|
||||
}
|
||||
spdlog::trace("Instance {} performing main fail callback", repl_instance_name);
|
||||
auto &repl_instance = FindReplicationInstance(repl_instance_name);
|
||||
repl_instance.OnFailPing();
|
||||
@@ -399,6 +454,9 @@ void CoordinatorInstance::MainFailCallback(std::string_view repl_instance_name)
|
||||
}
|
||||
|
||||
void CoordinatorInstance::MainSuccessCallback(std::string_view repl_instance_name) {
|
||||
if (!is_running_) {
|
||||
return;
|
||||
}
|
||||
spdlog::trace("Instance {} performing main successful callback", repl_instance_name);
|
||||
auto &repl_instance = FindReplicationInstance(repl_instance_name);
|
||||
|
||||
@@ -447,6 +505,9 @@ void CoordinatorInstance::MainSuccessCallback(std::string_view repl_instance_nam
|
||||
}
|
||||
|
||||
void CoordinatorInstance::ReplicaSuccessCallback(std::string_view repl_instance_name) {
|
||||
if (!is_running_) {
|
||||
return;
|
||||
}
|
||||
spdlog::trace("Instance {} performing replica successful callback", repl_instance_name);
|
||||
auto &repl_instance = FindReplicationInstance(repl_instance_name);
|
||||
|
||||
@@ -467,6 +528,9 @@ void CoordinatorInstance::ReplicaSuccessCallback(std::string_view repl_instance_
|
||||
}
|
||||
|
||||
void CoordinatorInstance::ReplicaFailCallback(std::string_view repl_instance_name) {
|
||||
if (!is_running_) {
|
||||
return;
|
||||
}
|
||||
spdlog::trace("Instance {} performing replica failure callback", repl_instance_name);
|
||||
auto &repl_instance = FindReplicationInstance(repl_instance_name);
|
||||
|
||||
|
||||
@@ -30,67 +30,77 @@ auto CoordinatorStateMachine::IsReplica(std::string_view instance_name) const ->
|
||||
return cluster_state_.IsReplica(instance_name);
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::CreateLog(nlohmann::json &&log) -> ptr<buffer> {
|
||||
auto const log_dump = log.dump();
|
||||
ptr<buffer> log_buf = buffer::alloc(sizeof(uint32_t) + log_dump.size());
|
||||
auto CoordinatorStateMachine::CreateLog(std::string_view log) -> ptr<buffer> {
|
||||
ptr<buffer> log_buf = buffer::alloc(sizeof(uint32_t) + log.size());
|
||||
buffer_serializer bs(log_buf);
|
||||
bs.put_str(log_dump);
|
||||
bs.put_str(log.data());
|
||||
return log_buf;
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::SerializeRegisterInstance(CoordinatorClientConfig const &config) -> ptr<buffer> {
|
||||
return CreateLog({{"action", RaftLogAction::REGISTER_REPLICATION_INSTANCE}, {"info", config}});
|
||||
auto const str_log = fmt::format("{}*register", config.ToString());
|
||||
return CreateLog(str_log);
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::SerializeUnregisterInstance(std::string_view instance_name) -> ptr<buffer> {
|
||||
return CreateLog({{"action", RaftLogAction::UNREGISTER_REPLICATION_INSTANCE}, {"info", instance_name}});
|
||||
auto const str_log = fmt::format("{}*unregister", instance_name);
|
||||
return CreateLog(str_log);
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::SerializeSetInstanceAsMain(std::string_view instance_name) -> ptr<buffer> {
|
||||
return CreateLog({{"action", RaftLogAction::SET_INSTANCE_AS_MAIN}, {"info", instance_name}});
|
||||
auto const str_log = fmt::format("{}*promote", instance_name);
|
||||
return CreateLog(str_log);
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::SerializeSetInstanceAsReplica(std::string_view instance_name) -> ptr<buffer> {
|
||||
return CreateLog({{"action", RaftLogAction::SET_INSTANCE_AS_REPLICA}, {"info", instance_name}});
|
||||
auto const str_log = fmt::format("{}*demote", instance_name);
|
||||
return CreateLog(str_log);
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::SerializeUpdateUUID(utils::UUID const &uuid) -> ptr<buffer> {
|
||||
return CreateLog({{"action", RaftLogAction::UPDATE_UUID}, {"info", uuid}});
|
||||
auto const str_log = fmt::format("{}*update_uuid", nlohmann::json{{"uuid", uuid}}.dump());
|
||||
return CreateLog(str_log);
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::DecodeLog(buffer &data) -> std::pair<TRaftLog, RaftLogAction> {
|
||||
buffer_serializer bs(data);
|
||||
auto const json = nlohmann::json::parse(bs.get_str());
|
||||
|
||||
auto const action = json["action"].get<RaftLogAction>();
|
||||
auto const &info = json["info"];
|
||||
auto const log_str = bs.get_str();
|
||||
auto const sep = log_str.find('*');
|
||||
auto const action = log_str.substr(sep + 1);
|
||||
auto const info = log_str.substr(0, sep);
|
||||
|
||||
switch (action) {
|
||||
case RaftLogAction::REGISTER_REPLICATION_INSTANCE:
|
||||
return {info.get<CoordinatorClientConfig>(), action};
|
||||
case RaftLogAction::UPDATE_UUID:
|
||||
return {info.get<utils::UUID>(), action};
|
||||
case RaftLogAction::UNREGISTER_REPLICATION_INSTANCE:
|
||||
case RaftLogAction::SET_INSTANCE_AS_MAIN:
|
||||
[[fallthrough]];
|
||||
case RaftLogAction::SET_INSTANCE_AS_REPLICA:
|
||||
return {info.get<std::string>(), action};
|
||||
if (action == "register") {
|
||||
return {CoordinatorClientConfig::FromString(info), RaftLogAction::REGISTER_REPLICATION_INSTANCE};
|
||||
}
|
||||
if (action == "unregister") {
|
||||
return {info, RaftLogAction::UNREGISTER_REPLICATION_INSTANCE};
|
||||
}
|
||||
if (action == "promote") {
|
||||
return {info, RaftLogAction::SET_INSTANCE_AS_MAIN};
|
||||
}
|
||||
if (action == "demote") {
|
||||
return {info, RaftLogAction::SET_INSTANCE_AS_REPLICA};
|
||||
}
|
||||
if (action == "update_uuid") {
|
||||
auto const json = nlohmann::json::parse(info);
|
||||
return {json.at("uuid").get<utils::UUID>(), RaftLogAction::UPDATE_UUID};
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown action");
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::pre_commit(ulong const /*log_idx*/, buffer & /*data*/) -> ptr<buffer> { return nullptr; }
|
||||
|
||||
auto CoordinatorStateMachine::commit(ulong const log_idx, buffer &data) -> ptr<buffer> {
|
||||
buffer_serializer bs(data);
|
||||
|
||||
auto const [parsed_data, log_action] = DecodeLog(data);
|
||||
cluster_state_.DoAction(parsed_data, log_action);
|
||||
last_committed_idx_ = log_idx;
|
||||
|
||||
// Return raft log number
|
||||
ptr<buffer> ret = buffer::alloc(sizeof(log_idx));
|
||||
buffer_serializer bs_ret(ret);
|
||||
bs_ret.put_u64(log_idx);
|
||||
return ret;
|
||||
last_committed_idx_ = log_idx;
|
||||
// TODO: (andi) Don't return nullptr
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto CoordinatorStateMachine::commit_config(ulong const log_idx, ptr<cluster_config> & /*new_conf*/) -> void {
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "json/json.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
|
||||
|
||||
// TODO: (andi) JSON serialization for RAFT log.
|
||||
struct CoordinatorClientConfig {
|
||||
std::string instance_name;
|
||||
std::string ip_address;
|
||||
@@ -43,11 +43,28 @@ struct CoordinatorClientConfig {
|
||||
}
|
||||
|
||||
struct ReplicationClientInfo {
|
||||
// TODO: (andi) Do we even need here instance_name for this struct?
|
||||
std::string instance_name;
|
||||
replication_coordination_glue::ReplicationMode replication_mode{};
|
||||
std::string replication_ip_address;
|
||||
uint16_t replication_port{};
|
||||
|
||||
auto ToString() const -> std::string {
|
||||
return fmt::format("{}#{}#{}#{}", instance_name, replication_ip_address, replication_port,
|
||||
replication_coordination_glue::ReplicationModeToString(replication_mode));
|
||||
}
|
||||
|
||||
// TODO: (andi) How can I make use of monadic parsers here?
|
||||
static auto FromString(std::string_view log) -> ReplicationClientInfo {
|
||||
ReplicationClientInfo replication_client_info;
|
||||
auto splitted = utils::Split(log, "#");
|
||||
replication_client_info.instance_name = splitted[0];
|
||||
replication_client_info.replication_ip_address = splitted[1];
|
||||
replication_client_info.replication_port = std::stoi(splitted[2]);
|
||||
replication_client_info.replication_mode = replication_coordination_glue::ReplicationModeFromString(splitted[3]);
|
||||
return replication_client_info;
|
||||
}
|
||||
|
||||
friend bool operator==(ReplicationClientInfo const &, ReplicationClientInfo const &) = default;
|
||||
};
|
||||
|
||||
@@ -62,6 +79,25 @@ struct CoordinatorClientConfig {
|
||||
|
||||
std::optional<SSL> ssl;
|
||||
|
||||
auto ToString() const -> std::string {
|
||||
return fmt::format("{}|{}|{}|{}|{}|{}|{}", instance_name, ip_address, port,
|
||||
instance_health_check_frequency_sec.count(), instance_down_timeout_sec.count(),
|
||||
instance_get_uuid_frequency_sec.count(), replication_client_info.ToString());
|
||||
}
|
||||
|
||||
static auto FromString(std::string_view log) -> CoordinatorClientConfig {
|
||||
CoordinatorClientConfig config;
|
||||
auto splitted = utils::Split(log, "|");
|
||||
config.instance_name = splitted[0];
|
||||
config.ip_address = splitted[1];
|
||||
config.port = std::stoi(splitted[2]);
|
||||
config.instance_health_check_frequency_sec = std::chrono::seconds(std::stoi(splitted[3]));
|
||||
config.instance_down_timeout_sec = std::chrono::seconds(std::stoi(splitted[4]));
|
||||
config.instance_get_uuid_frequency_sec = std::chrono::seconds(std::stoi(splitted[5]));
|
||||
config.replication_client_info = ReplicationClientInfo::FromString(splitted[6]);
|
||||
return config;
|
||||
}
|
||||
|
||||
friend bool operator==(CoordinatorClientConfig const &, CoordinatorClientConfig const &) = default;
|
||||
};
|
||||
|
||||
@@ -83,11 +119,5 @@ struct CoordinatorServerConfig {
|
||||
friend bool operator==(CoordinatorServerConfig const &, CoordinatorServerConfig const &) = default;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, CoordinatorClientConfig const &config);
|
||||
void from_json(nlohmann::json const &j, CoordinatorClientConfig &config);
|
||||
|
||||
void to_json(nlohmann::json &j, ReplClientInfo const &config);
|
||||
void from_json(nlohmann::json const &j, ReplClientInfo &config);
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
|
||||
@@ -90,7 +90,7 @@ struct UnregisterReplicaReq {
|
||||
static void Load(UnregisterReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(UnregisterReplicaReq const &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit UnregisterReplicaReq(std::string_view inst_name) : instance_name(inst_name) {}
|
||||
explicit UnregisterReplicaReq(std::string instance_name) : instance_name(std::move(instance_name)) {}
|
||||
|
||||
UnregisterReplicaReq() = default;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include <flags/replication.hpp>
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "nuraft/coordinator_state_machine.hpp"
|
||||
#include "nuraft/coordinator_state_manager.hpp"
|
||||
|
||||
@@ -80,8 +79,9 @@ class RaftState {
|
||||
|
||||
private:
|
||||
// TODO: (andi) I think variables below can be abstracted/clean them.
|
||||
io::network::Endpoint raft_endpoint_;
|
||||
uint32_t raft_server_id_;
|
||||
uint32_t raft_port_;
|
||||
std::string raft_address_;
|
||||
|
||||
ptr<CoordinatorStateMachine> state_machine_;
|
||||
ptr<CoordinatorStateManager> state_manager_;
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <libnuraft/nuraft.hxx>
|
||||
#include <range/v3/view.hpp>
|
||||
#include "json/json.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
@@ -34,16 +33,9 @@ using replication_coordination_glue::ReplicationRole;
|
||||
|
||||
struct InstanceState {
|
||||
CoordinatorClientConfig config;
|
||||
ReplicationRole status;
|
||||
|
||||
friend auto operator==(InstanceState const &lhs, InstanceState const &rhs) -> bool {
|
||||
return lhs.config == rhs.config && lhs.status == rhs.status;
|
||||
}
|
||||
ReplicationRole role;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, InstanceState const &instance_state);
|
||||
void from_json(nlohmann::json const &j, InstanceState &instance_state);
|
||||
|
||||
using TRaftLog = std::variant<CoordinatorClientConfig, std::string, utils::UUID>;
|
||||
|
||||
using nuraft::buffer;
|
||||
@@ -53,8 +45,6 @@ using nuraft::ptr;
|
||||
class CoordinatorClusterState {
|
||||
public:
|
||||
CoordinatorClusterState() = default;
|
||||
explicit CoordinatorClusterState(std::map<std::string, InstanceState, std::less<>> instances);
|
||||
|
||||
CoordinatorClusterState(CoordinatorClusterState const &);
|
||||
CoordinatorClusterState &operator=(CoordinatorClusterState const &);
|
||||
|
||||
@@ -70,7 +60,7 @@ class CoordinatorClusterState {
|
||||
|
||||
auto IsReplica(std::string_view instance_name) const -> bool;
|
||||
|
||||
auto InsertInstance(std::string instance_name, InstanceState instance_state) -> void;
|
||||
auto InsertInstance(std::string_view instance_name, ReplicationRole role) -> void;
|
||||
|
||||
auto DoAction(TRaftLog log_entry, RaftLogAction log_action) -> void;
|
||||
|
||||
@@ -83,7 +73,7 @@ class CoordinatorClusterState {
|
||||
auto GetUUID() const -> utils::UUID;
|
||||
|
||||
private:
|
||||
std::map<std::string, InstanceState, std::less<>> instances_{};
|
||||
std::map<std::string, InstanceState, std::less<>> instance_roles_;
|
||||
utils::UUID uuid_{};
|
||||
mutable utils::ResourceLock log_lock_{};
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ class CoordinatorStateMachine : public state_machine {
|
||||
auto IsMain(std::string_view instance_name) const -> bool;
|
||||
auto IsReplica(std::string_view instance_name) const -> bool;
|
||||
|
||||
static auto CreateLog(nlohmann::json &&log) -> ptr<buffer>;
|
||||
static auto CreateLog(std::string_view log) -> ptr<buffer>;
|
||||
static auto SerializeRegisterInstance(CoordinatorClientConfig const &config) -> ptr<buffer>;
|
||||
static auto SerializeUnregisterInstance(std::string_view instance_name) -> ptr<buffer>;
|
||||
static auto SerializeSetInstanceAsMain(std::string_view instance_name) -> ptr<buffer>;
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
enum class RaftLogAction : uint8_t {
|
||||
@@ -30,13 +28,26 @@ enum class RaftLogAction : uint8_t {
|
||||
UPDATE_UUID
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(RaftLogAction, {
|
||||
{RaftLogAction::REGISTER_REPLICATION_INSTANCE, "register"},
|
||||
{RaftLogAction::UNREGISTER_REPLICATION_INSTANCE, "unregister"},
|
||||
{RaftLogAction::SET_INSTANCE_AS_MAIN, "promote"},
|
||||
{RaftLogAction::SET_INSTANCE_AS_REPLICA, "demote"},
|
||||
{RaftLogAction::UPDATE_UUID, "update_uuid"},
|
||||
})
|
||||
inline auto ParseRaftLogAction(std::string_view action) -> RaftLogAction {
|
||||
if (action == "register") {
|
||||
return RaftLogAction::REGISTER_REPLICATION_INSTANCE;
|
||||
}
|
||||
if (action == "unregister") {
|
||||
return RaftLogAction::UNREGISTER_REPLICATION_INSTANCE;
|
||||
}
|
||||
if (action == "promote") {
|
||||
return RaftLogAction::SET_INSTANCE_AS_MAIN;
|
||||
}
|
||||
if (action == "demote") {
|
||||
return RaftLogAction::SET_INSTANCE_AS_REPLICA;
|
||||
}
|
||||
|
||||
if (action == "update_uuid") {
|
||||
return RaftLogAction::UPDATE_UUID;
|
||||
}
|
||||
|
||||
throw InvalidRaftLogActionException("Invalid Raft log action: {}.", action);
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
|
||||
@@ -32,10 +32,12 @@ using raft_result = cmd_result<ptr<buffer>>;
|
||||
|
||||
RaftState::RaftState(BecomeLeaderCb become_leader_cb, BecomeFollowerCb become_follower_cb, uint32_t raft_server_id,
|
||||
uint32_t raft_port, std::string raft_address)
|
||||
: raft_endpoint_(raft_address, raft_port),
|
||||
raft_server_id_(raft_server_id),
|
||||
: raft_server_id_(raft_server_id),
|
||||
raft_port_(raft_port),
|
||||
raft_address_(std::move(raft_address)),
|
||||
state_machine_(cs_new<CoordinatorStateMachine>()),
|
||||
state_manager_(cs_new<CoordinatorStateManager>(raft_server_id_, raft_endpoint_.SocketAddress())),
|
||||
state_manager_(
|
||||
cs_new<CoordinatorStateManager>(raft_server_id_, raft_address_ + ":" + std::to_string(raft_port_))),
|
||||
logger_(nullptr),
|
||||
become_leader_cb_(std::move(become_leader_cb)),
|
||||
become_follower_cb_(std::move(become_follower_cb)) {}
|
||||
@@ -57,6 +59,8 @@ auto RaftState::InitRaftServer() -> void {
|
||||
|
||||
raft_server::init_options init_opts;
|
||||
init_opts.raft_callback_ = [this](cb_func::Type event_type, cb_func::Param *param) -> nuraft::CbReturnCode {
|
||||
spdlog::info("Received some message, my id {}, leader id {}, event_type {}, thread_id {}", param->myId,
|
||||
param->leaderId, event_type, std::this_thread::get_id());
|
||||
if (event_type == cb_func::BecomeLeader) {
|
||||
spdlog::info("Node {} became leader", param->leaderId);
|
||||
become_leader_cb_();
|
||||
@@ -69,11 +73,11 @@ auto RaftState::InitRaftServer() -> void {
|
||||
|
||||
raft_launcher launcher;
|
||||
|
||||
raft_server_ =
|
||||
launcher.init(state_machine_, state_manager_, logger_, raft_endpoint_.port, asio_opts, params, init_opts);
|
||||
raft_server_ = launcher.init(state_machine_, state_manager_, logger_, static_cast<int>(raft_port_), asio_opts, params,
|
||||
init_opts);
|
||||
|
||||
if (!raft_server_) {
|
||||
throw RaftServerStartException("Failed to launch raft server on {}", raft_endpoint_.SocketAddress());
|
||||
throw RaftServerStartException("Failed to launch raft server on {}:{}", raft_address_, raft_port_);
|
||||
}
|
||||
|
||||
auto maybe_stop = utils::ResettableCounter<20>();
|
||||
@@ -84,7 +88,7 @@ auto RaftState::InitRaftServer() -> void {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
} while (!maybe_stop());
|
||||
|
||||
throw RaftServerStartException("Failed to initialize raft server on {}", raft_endpoint_.SocketAddress());
|
||||
throw RaftServerStartException("Failed to initialize raft server on {}:{}", raft_address_, raft_port_);
|
||||
}
|
||||
|
||||
auto RaftState::MakeRaftState(BecomeLeaderCb &&become_leader_cb, BecomeFollowerCb &&become_follower_cb) -> RaftState {
|
||||
@@ -100,11 +104,9 @@ auto RaftState::MakeRaftState(BecomeLeaderCb &&become_leader_cb, BecomeFollowerC
|
||||
|
||||
RaftState::~RaftState() { launcher_.shutdown(); }
|
||||
|
||||
auto RaftState::InstanceName() const -> std::string {
|
||||
return fmt::format("coordinator_{}", std::to_string(raft_server_id_));
|
||||
}
|
||||
auto RaftState::InstanceName() const -> std::string { return "coordinator_" + std::to_string(raft_server_id_); }
|
||||
|
||||
auto RaftState::RaftSocketAddress() const -> std::string { return raft_endpoint_.SocketAddress(); }
|
||||
auto RaftState::RaftSocketAddress() const -> std::string { return raft_address_ + ":" + std::to_string(raft_port_); }
|
||||
|
||||
auto RaftState::AddCoordinatorInstance(uint32_t raft_server_id, uint32_t raft_port, std::string_view raft_address)
|
||||
-> void {
|
||||
|
||||
@@ -118,14 +118,9 @@ void InMemoryReplicationHandlers::Register(dbms::DbmsHandler *dbms_handler, repl
|
||||
});
|
||||
server.rpc_server_.Register<replication_coordination_glue::SwapMainUUIDRpc>(
|
||||
[&data, dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SwapMainUUIDRpc");
|
||||
spdlog::debug("Received SwapMainUUIDHandler");
|
||||
InMemoryReplicationHandlers::SwapMainUUIDHandler(dbms_handler, data, req_reader, res_builder);
|
||||
});
|
||||
server.rpc_server_.Register<storage::replication::ForceResetStorageRpc>(
|
||||
[&data, dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received ForceResetStorageRpc");
|
||||
InMemoryReplicationHandlers::ForceResetStorageHandler(dbms_handler, data.uuid_, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
|
||||
void InMemoryReplicationHandlers::SwapMainUUIDHandler(dbms::DbmsHandler *dbms_handler,
|
||||
@@ -334,78 +329,6 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
|
||||
spdlog::debug("Replication recovery from snapshot finished!");
|
||||
}
|
||||
|
||||
void InMemoryReplicationHandlers::ForceResetStorageHandler(dbms::DbmsHandler *dbms_handler,
|
||||
const std::optional<utils::UUID> ¤t_main_uuid,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
storage::replication::ForceResetStorageReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_uuid);
|
||||
if (!db_acc) {
|
||||
storage::replication::ForceResetStorageRes res{false, 0};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
if (!current_main_uuid.has_value() || req.main_uuid != current_main_uuid) [[unlikely]] {
|
||||
LogWrongMain(current_main_uuid, req.main_uuid, storage::replication::SnapshotReq::kType.name);
|
||||
storage::replication::ForceResetStorageRes res{false, 0};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
storage::replication::Decoder decoder(req_reader);
|
||||
|
||||
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
|
||||
|
||||
auto storage_guard = std::unique_lock{storage->main_lock_};
|
||||
|
||||
// Clear the database
|
||||
storage->vertices_.clear();
|
||||
storage->edges_.clear();
|
||||
storage->commit_log_.reset();
|
||||
storage->commit_log_.emplace();
|
||||
|
||||
storage->constraints_.existence_constraints_ = std::make_unique<storage::ExistenceConstraints>();
|
||||
storage->constraints_.unique_constraints_ = std::make_unique<storage::InMemoryUniqueConstraints>();
|
||||
storage->indices_.label_index_ = std::make_unique<storage::InMemoryLabelIndex>();
|
||||
storage->indices_.label_property_index_ = std::make_unique<storage::InMemoryLabelPropertyIndex>();
|
||||
|
||||
// Fine since we will force push when reading from WAL just random epoch with 0 timestamp, as it should be if it
|
||||
// acted as MAIN before
|
||||
storage->repl_storage_state_.epoch_.SetEpoch(std::string(utils::UUID{}));
|
||||
storage->repl_storage_state_.last_commit_timestamp_ = 0;
|
||||
|
||||
storage->repl_storage_state_.history.clear();
|
||||
storage->vertex_id_ = 0;
|
||||
storage->edge_id_ = 0;
|
||||
storage->timestamp_ = storage::kTimestampInitialId;
|
||||
|
||||
storage->CollectGarbage<true>(std::move(storage_guard), false);
|
||||
storage->vertices_.run_gc();
|
||||
storage->edges_.run_gc();
|
||||
|
||||
storage::replication::ForceResetStorageRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
|
||||
slk::Save(res, res_builder);
|
||||
|
||||
spdlog::trace("Deleting old snapshot files.");
|
||||
// Delete other durability files
|
||||
auto snapshot_files = storage::durability::GetSnapshotFiles(storage->recovery_.snapshot_directory_, storage->uuid_);
|
||||
for (const auto &[path, uuid, _] : snapshot_files) {
|
||||
spdlog::trace("Deleting snapshot file {}", path);
|
||||
storage->file_retainer_.DeleteFile(path);
|
||||
}
|
||||
|
||||
spdlog::trace("Deleting old WAL files.");
|
||||
auto wal_files = storage::durability::GetWalFiles(storage->recovery_.wal_directory_, storage->uuid_);
|
||||
if (wal_files) {
|
||||
for (const auto &wal_file : *wal_files) {
|
||||
spdlog::trace("Deleting WAL file {}", wal_file.path);
|
||||
storage->file_retainer_.DeleteFile(wal_file.path);
|
||||
}
|
||||
|
||||
storage->wal_file_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handler,
|
||||
const std::optional<utils::UUID> ¤t_main_uuid,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
|
||||
@@ -48,9 +48,6 @@ class InMemoryReplicationHandlers {
|
||||
|
||||
static void SwapMainUUIDHandler(dbms::DbmsHandler *dbms_handler, replication::RoleReplicaData &role_replica_data,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
static void ForceResetStorageHandler(dbms::DbmsHandler *dbms_handler,
|
||||
const std::optional<utils::UUID> ¤t_main_uuid, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
|
||||
static void LoadWal(storage::InMemoryStorage *storage, storage::replication::Decoder *decoder);
|
||||
|
||||
|
||||
@@ -22,15 +22,113 @@
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view delimiter = ":";
|
||||
} // namespace
|
||||
|
||||
namespace memgraph::io::network {
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
Endpoint::Endpoint(needs_resolving_t, std::string hostname, uint16_t port)
|
||||
: address(std::move(hostname)), port(port), family{GetIpFamily(address)} {}
|
||||
Endpoint::IpFamily Endpoint::GetIpFamily(std::string_view address) {
|
||||
in_addr addr4;
|
||||
in6_addr addr6;
|
||||
int ipv4_result = inet_pton(AF_INET, address.data(), &addr4);
|
||||
int ipv6_result = inet_pton(AF_INET6, address.data(), &addr6);
|
||||
if (ipv4_result == 1) {
|
||||
return IpFamily::IP4;
|
||||
}
|
||||
if (ipv6_result == 1) {
|
||||
return IpFamily::IP6;
|
||||
}
|
||||
return IpFamily::NONE;
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrIpAddress(
|
||||
std::string_view address, const std::optional<uint16_t> default_port) {
|
||||
/// expected address format:
|
||||
/// - "ip_address:port_number"
|
||||
/// - "ip_address"
|
||||
/// We parse the address first. If it's an IP address, a default port must
|
||||
// be given, or we return nullopt. If it's a socket address, we try to parse
|
||||
// it into an ip address and a port number; even if a default port is given,
|
||||
// it won't be used, as we expect that it is given in the address string.
|
||||
const std::string delimiter = ":";
|
||||
std::string ip_address;
|
||||
|
||||
std::vector<std::string> parts = utils::Split(address, delimiter);
|
||||
if (parts.size() == 1) {
|
||||
if (default_port) {
|
||||
if (GetIpFamily(address) == IpFamily::NONE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::pair{std::string(address), *default_port}; // TODO: (andi) Optimize throughout the code
|
||||
}
|
||||
} else if (parts.size() == 2) {
|
||||
ip_address = std::move(parts[0]);
|
||||
if (GetIpFamily(ip_address) == IpFamily::NONE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
int64_t int_port{0};
|
||||
try {
|
||||
int_port = utils::ParseInt(parts[1]);
|
||||
} catch (utils::BasicException &e) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number {}.", parts[1], "https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (int_port < 0) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number {}. The port number must be a positive integer.",
|
||||
int_port, "https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (int_port > std::numeric_limits<uint16_t>::max()) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number. The port number exceedes the maximum possible size.",
|
||||
"https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::pair{ip_address, static_cast<uint16_t>(int_port)};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseHostname(
|
||||
std::string_view address, const std::optional<uint16_t> default_port = {}) {
|
||||
const std::string delimiter = ":";
|
||||
std::string ip_address;
|
||||
std::vector<std::string> parts = utils::Split(address, delimiter);
|
||||
if (parts.size() == 1) {
|
||||
if (default_port) {
|
||||
if (!IsResolvableAddress(address, *default_port)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::pair{std::string(address), *default_port}; // TODO: (andi) Optimize throughout the code
|
||||
}
|
||||
} else if (parts.size() == 2) {
|
||||
int64_t int_port{0};
|
||||
auto hostname = std::move(parts[0]);
|
||||
try {
|
||||
int_port = utils::ParseInt(parts[1]);
|
||||
} catch (utils::BasicException &e) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number {}.", parts[1], "https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (int_port < 0) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number {}. The port number must be a positive integer.",
|
||||
int_port, "https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (int_port > std::numeric_limits<uint16_t>::max()) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number. The port number exceedes the maximum possible size.",
|
||||
"https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (IsResolvableAddress(hostname, static_cast<uint16_t>(int_port))) {
|
||||
return std::pair{hostname, static_cast<u_int16_t>(int_port)};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string Endpoint::SocketAddress() const {
|
||||
auto ip_address = address.empty() ? "EMPTY" : address;
|
||||
return ip_address + ":" + std::to_string(port);
|
||||
}
|
||||
|
||||
Endpoint::Endpoint(std::string ip_address, uint16_t port) : address(std::move(ip_address)), port(port) {
|
||||
IpFamily ip_family = GetIpFamily(address);
|
||||
@@ -40,23 +138,9 @@ Endpoint::Endpoint(std::string ip_address, uint16_t port) : address(std::move(ip
|
||||
family = ip_family;
|
||||
}
|
||||
|
||||
std::string Endpoint::SocketAddress() const { return fmt::format("{}:{}", address, port); }
|
||||
|
||||
Endpoint::IpFamily Endpoint::GetIpFamily(std::string_view address) {
|
||||
// Ensure null-terminated
|
||||
auto const tmp = std::string(address);
|
||||
in_addr addr4;
|
||||
in6_addr addr6;
|
||||
int ipv4_result = inet_pton(AF_INET, tmp.c_str(), &addr4);
|
||||
int ipv6_result = inet_pton(AF_INET6, tmp.c_str(), &addr6);
|
||||
if (ipv4_result == 1) {
|
||||
return IpFamily::IP4;
|
||||
}
|
||||
if (ipv6_result == 1) {
|
||||
return IpFamily::IP6;
|
||||
}
|
||||
return IpFamily::NONE;
|
||||
}
|
||||
// NOLINTNEXTLINE
|
||||
Endpoint::Endpoint(needs_resolving_t, std::string hostname, uint16_t port)
|
||||
: address(std::move(hostname)), port(port), family{GetIpFamily(address)} {}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const Endpoint &endpoint) {
|
||||
// no need to cover the IpFamily::NONE case, as you can't even construct an
|
||||
@@ -69,7 +153,6 @@ std::ostream &operator<<(std::ostream &os, const Endpoint &endpoint) {
|
||||
return os << endpoint.address << ":" << endpoint.port;
|
||||
}
|
||||
|
||||
// NOTE: Intentional copy to ensure null-terminated string
|
||||
bool Endpoint::IsResolvableAddress(std::string_view address, uint16_t port) {
|
||||
addrinfo hints{
|
||||
.ai_flags = AI_PASSIVE,
|
||||
@@ -77,65 +160,28 @@ bool Endpoint::IsResolvableAddress(std::string_view address, uint16_t port) {
|
||||
.ai_socktype = SOCK_STREAM // TCP socket
|
||||
};
|
||||
addrinfo *info = nullptr;
|
||||
auto status = getaddrinfo(std::string(address).c_str(), std::to_string(port).c_str(), &hints, &info);
|
||||
auto status = getaddrinfo(address.data(), std::to_string(port).c_str(), &hints, &info);
|
||||
if (info) freeaddrinfo(info);
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
std::optional<ParsedAddress> Endpoint::ParseSocketOrAddress(std::string_view address,
|
||||
std::optional<uint16_t> default_port) {
|
||||
auto const parts = utils::SplitView(address, delimiter);
|
||||
|
||||
if (parts.size() > 2) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto const port = [default_port, &parts]() -> std::optional<uint16_t> {
|
||||
if (parts.size() == 2) {
|
||||
return static_cast<uint16_t>(utils::ParseInt(parts[1]));
|
||||
std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrAddress(
|
||||
std::string_view address, const std::optional<uint16_t> default_port) {
|
||||
const std::string delimiter = ":";
|
||||
std::vector<std::string> parts = utils::Split(address, delimiter);
|
||||
if (parts.size() == 1) {
|
||||
if (GetIpFamily(address) == IpFamily::NONE) {
|
||||
return ParseHostname(address, default_port);
|
||||
}
|
||||
return default_port;
|
||||
}();
|
||||
|
||||
if (!ValidatePort(port)) {
|
||||
return std::nullopt;
|
||||
return ParseSocketOrIpAddress(address, default_port);
|
||||
}
|
||||
|
||||
auto const addr = [address, &parts]() {
|
||||
if (parts.size() == 2) {
|
||||
return parts[0];
|
||||
if (parts.size() == 2) {
|
||||
if (GetIpFamily(parts[0]) == IpFamily::NONE) {
|
||||
return ParseHostname(address, default_port);
|
||||
}
|
||||
return address;
|
||||
}();
|
||||
|
||||
if (GetIpFamily(addr) == IpFamily::NONE) {
|
||||
if (IsResolvableAddress(addr, *port)) { // NOLINT
|
||||
return std::pair{addr, *port}; // NOLINT
|
||||
}
|
||||
return std::nullopt;
|
||||
return ParseSocketOrIpAddress(address, default_port);
|
||||
}
|
||||
|
||||
return std::pair{addr, *port}; // NOLINT
|
||||
}
|
||||
|
||||
auto Endpoint::ValidatePort(std::optional<uint16_t> port) -> bool {
|
||||
if (!port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (port < 0) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number {}. The port number must be a positive integer.", *port,
|
||||
"https://memgr.ph/ports"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (port > std::numeric_limits<uint16_t>::max()) {
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number. The port number exceedes the maximum possible size.",
|
||||
"https://memgr.ph/ports"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace memgraph::io::network
|
||||
|
||||
@@ -19,8 +19,11 @@
|
||||
|
||||
namespace memgraph::io::network {
|
||||
|
||||
using ParsedAddress = std::pair<std::string_view, uint16_t>;
|
||||
|
||||
/**
|
||||
* This class represents a network endpoint that is used in Socket.
|
||||
* It is used when connecting to an address and to get the current
|
||||
* connection address.
|
||||
*/
|
||||
struct Endpoint {
|
||||
static const struct needs_resolving_t {
|
||||
} needs_resolving;
|
||||
@@ -28,35 +31,59 @@ struct Endpoint {
|
||||
Endpoint() = default;
|
||||
Endpoint(std::string ip_address, uint16_t port);
|
||||
Endpoint(needs_resolving_t, std::string hostname, uint16_t port);
|
||||
|
||||
Endpoint(Endpoint const &) = default;
|
||||
Endpoint(Endpoint &&) noexcept = default;
|
||||
|
||||
Endpoint &operator=(Endpoint const &) = default;
|
||||
Endpoint &operator=(Endpoint &&) noexcept = default;
|
||||
|
||||
~Endpoint() = default;
|
||||
|
||||
enum class IpFamily : std::uint8_t { NONE, IP4, IP6 };
|
||||
|
||||
static std::optional<ParsedAddress> ParseSocketOrAddress(std::string_view address,
|
||||
std::optional<uint16_t> default_port = {});
|
||||
|
||||
std::string SocketAddress() const;
|
||||
|
||||
bool operator==(const Endpoint &other) const = default;
|
||||
friend std::ostream &operator<<(std::ostream &os, const Endpoint &endpoint);
|
||||
|
||||
std::string address;
|
||||
uint16_t port{0};
|
||||
IpFamily family{IpFamily::NONE};
|
||||
|
||||
bool operator==(const Endpoint &other) const = default;
|
||||
friend std::ostream &operator<<(std::ostream &os, const Endpoint &endpoint);
|
||||
static std::optional<std::pair<std::string, uint16_t>> ParseSocketOrAddress(
|
||||
std::string_view address, std::optional<uint16_t> default_port = {});
|
||||
|
||||
/**
|
||||
* Tries to parse the given string as either a socket address or ip address.
|
||||
* Expected address format:
|
||||
* - "ip_address:port_number"
|
||||
* - "ip_address"
|
||||
* We parse the address first. If it's an IP address, a default port must
|
||||
* be given, or we return nullopt. If it's a socket address, we try to parse
|
||||
* it into an ip address and a port number; even if a default port is given,
|
||||
* it won't be used, as we expect that it is given in the address string.
|
||||
*/
|
||||
static std::optional<std::pair<std::string, uint16_t>> ParseSocketOrIpAddress(
|
||||
std::string_view address, std::optional<uint16_t> default_port = {});
|
||||
|
||||
/**
|
||||
* Tries to parse given string as either socket address or hostname.
|
||||
* Expected address format:
|
||||
* - "hostname:port_number"
|
||||
* - "hostname"
|
||||
* After we parse hostname and port we try to resolve the hostname into an ip_address.
|
||||
*/
|
||||
static std::optional<std::pair<std::string, uint16_t>> ParseHostname(std::string_view address,
|
||||
std::optional<uint16_t> default_port);
|
||||
|
||||
private:
|
||||
static IpFamily GetIpFamily(std::string_view address);
|
||||
|
||||
static bool IsResolvableAddress(std::string_view address, uint16_t port);
|
||||
|
||||
static auto ValidatePort(std::optional<uint16_t> port) -> bool;
|
||||
/**
|
||||
* Tries to resolve hostname to its corresponding IP address.
|
||||
* Given a DNS hostname, this function performs resolution and returns
|
||||
* the IP address associated with the hostname.
|
||||
*/
|
||||
static std::string ResolveHostnameIntoIpAddress(const std::string &address, uint16_t port);
|
||||
};
|
||||
|
||||
} // namespace memgraph::io::network
|
||||
|
||||
@@ -352,7 +352,6 @@ cypherKeyword : ALL
|
||||
| BY
|
||||
| CALL
|
||||
| CASE
|
||||
| COALESCE
|
||||
| CONSTRAINT
|
||||
| CONTAINS
|
||||
| COUNT
|
||||
@@ -363,7 +362,6 @@ cypherKeyword : ALL
|
||||
| DESCENDING
|
||||
| DETACH
|
||||
| DISTINCT
|
||||
| DROP
|
||||
| ELSE
|
||||
| END
|
||||
| ENDS
|
||||
@@ -375,15 +373,11 @@ cypherKeyword : ALL
|
||||
| IN
|
||||
| INDEX
|
||||
| INFO
|
||||
| INSTANCE
|
||||
| IS
|
||||
| KB
|
||||
| KEY
|
||||
| LIMIT
|
||||
| L_SKIP
|
||||
| MATCH
|
||||
| MB
|
||||
| MEMORY
|
||||
| MERGE
|
||||
| NODE
|
||||
| NONE
|
||||
@@ -396,7 +390,6 @@ cypherKeyword : ALL
|
||||
| PROFILE
|
||||
| QUERY
|
||||
| REDUCE
|
||||
| REGISTER
|
||||
| REMOVE
|
||||
| RETURN
|
||||
| SET
|
||||
@@ -408,7 +401,6 @@ cypherKeyword : ALL
|
||||
| TRUE
|
||||
| UNION
|
||||
| UNIQUE
|
||||
| UNLIMITED
|
||||
| UNWIND
|
||||
| WHEN
|
||||
| WHERE
|
||||
|
||||
@@ -32,9 +32,8 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| BATCH_LIMIT
|
||||
| BATCH_SIZE
|
||||
| BEFORE
|
||||
| BUILD
|
||||
| BOOTSTRAP_SERVERS
|
||||
| CALL
|
||||
| BUILD
|
||||
| CHECK
|
||||
| CLEAR
|
||||
| COMMIT
|
||||
@@ -42,20 +41,16 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| CONFIG
|
||||
| CONFIGS
|
||||
| CONSUMER_GROUP
|
||||
| COORDINATOR
|
||||
| CREATE_DELETE
|
||||
| CREDENTIALS
|
||||
| CSV
|
||||
| DATA
|
||||
| DO
|
||||
| DELIMITER
|
||||
| DATABASE
|
||||
| DATABASES
|
||||
| DENY
|
||||
| DIRECTORY
|
||||
| DROP
|
||||
| DO
|
||||
| DUMP
|
||||
| DURABILITY
|
||||
| EDGE
|
||||
| EDGE_TYPES
|
||||
| EXECUTE
|
||||
@@ -63,21 +58,20 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| FOR
|
||||
| FOREACH
|
||||
| FREE
|
||||
| FREE_MEMORY
|
||||
| FROM
|
||||
| GLOBAL
|
||||
| GRANT
|
||||
| GRAPH
|
||||
| GRANTS
|
||||
| GRANT
|
||||
| HEADER
|
||||
| IDENTIFIED
|
||||
| IGNORE
|
||||
| INSTANCE
|
||||
| INSTANCES
|
||||
| NODE_LABELS
|
||||
| NULLIF
|
||||
| IMPORT
|
||||
| INACTIVE
|
||||
| IN_MEMORY_ANALYTICAL
|
||||
| IN_MEMORY_TRANSACTIONAL
|
||||
| INSTANCE
|
||||
| INSTANCES
|
||||
| ISOLATION
|
||||
| KAFKA
|
||||
| LABELS
|
||||
@@ -86,22 +80,14 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| LOCK
|
||||
| MAIN
|
||||
| MODE
|
||||
| MODULE_READ
|
||||
| MODULE_WRITE
|
||||
| MULTI_DATABASE_EDIT
|
||||
| MULTI_DATABASE_USE
|
||||
| NEXT
|
||||
| NO
|
||||
| NODE_LABELS
|
||||
| NOTHING
|
||||
| ON_DISK_TRANSACTIONAL
|
||||
| NULLIF
|
||||
| PASSWORD
|
||||
| PULSAR
|
||||
| PORT
|
||||
| PRIVILEGES
|
||||
| PULSAR
|
||||
| READ
|
||||
| READ_FILE
|
||||
| REGISTER
|
||||
| REPLICA
|
||||
| REPLICAS
|
||||
@@ -110,42 +96,34 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| ROLE
|
||||
| ROLES
|
||||
| QUOTE
|
||||
| SERVER
|
||||
| SERVICE_URL
|
||||
| SESSION
|
||||
| SETTING
|
||||
| SETTINGS
|
||||
| SNAPSHOT
|
||||
| START
|
||||
| STATISTICS
|
||||
| STATS
|
||||
| STATUS
|
||||
| STOP
|
||||
| STORAGE
|
||||
| STORAGE_MODE
|
||||
| STREAM
|
||||
| STREAMS
|
||||
| SYNC
|
||||
| TERMINATE
|
||||
| TIMEOUT
|
||||
| TO
|
||||
| TOPICS
|
||||
| TRANSACTION
|
||||
| TRANSACTION_MANAGEMENT
|
||||
| TRANSACTIONS
|
||||
| TRANSFORM
|
||||
| TRIGGER
|
||||
| TRIGGERS
|
||||
| UNCOMMITTED
|
||||
| UNLOCK
|
||||
| UNREGISTER
|
||||
| UPDATE
|
||||
| USE
|
||||
| USER
|
||||
| USERS
|
||||
| USING
|
||||
| VERSION
|
||||
| WEBSOCKET
|
||||
| TERMINATE
|
||||
| TRANSACTIONS
|
||||
;
|
||||
|
||||
symbolicName : UnescapedSymbolicName
|
||||
|
||||
@@ -355,7 +355,7 @@ class ReplQueryHandler {
|
||||
const auto replication_config =
|
||||
replication::ReplicationClientConfig{.name = name,
|
||||
.mode = repl_mode,
|
||||
.ip_address = std::string(ip),
|
||||
.ip_address = ip,
|
||||
.port = port,
|
||||
.replica_check_frequency = replica_check_frequency,
|
||||
.ssl = std::nullopt};
|
||||
@@ -454,12 +454,12 @@ class CoordQueryHandler final : public query::CoordinatorQueryHandler {
|
||||
const auto repl_config = coordination::CoordinatorClientConfig::ReplicationClientInfo{
|
||||
.instance_name = std::string(instance_name),
|
||||
.replication_mode = convertFromCoordinatorToReplicationMode(sync_mode),
|
||||
.replication_ip_address = std::string(replication_ip),
|
||||
.replication_ip_address = replication_ip,
|
||||
.replication_port = replication_port};
|
||||
|
||||
auto coordinator_client_config =
|
||||
coordination::CoordinatorClientConfig{.instance_name = std::string(instance_name),
|
||||
.ip_address = std::string(coordinator_server_ip),
|
||||
.ip_address = coordinator_server_ip,
|
||||
.port = coordinator_server_port,
|
||||
.instance_health_check_frequency_sec = instance_check_frequency,
|
||||
.instance_down_timeout_sec = instance_down_timeout,
|
||||
@@ -1212,7 +1212,7 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
};
|
||||
|
||||
notifications->emplace_back(
|
||||
SeverityLevel::INFO, NotificationCode::REGISTER_REPLICATION_INSTANCE,
|
||||
SeverityLevel::INFO, NotificationCode::REGISTER_COORDINATOR_SERVER,
|
||||
fmt::format("Coordinator has registered coordinator server on {} for instance {}.",
|
||||
coordinator_socket_address_tv.ValueString(), coordinator_query->instance_name_));
|
||||
return callback;
|
||||
@@ -1263,7 +1263,7 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
TypedValue{status.coord_socket_address}, TypedValue{status.health}, TypedValue{status.cluster_role}};
|
||||
};
|
||||
|
||||
return utils::fmap(instances, converter);
|
||||
return utils::fmap(converter, instances);
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
case NotificationCode::REGISTER_REPLICA:
|
||||
return "RegisterReplica"sv;
|
||||
#ifdef MG_ENTERPRISE
|
||||
case NotificationCode::REGISTER_REPLICATION_INSTANCE:
|
||||
return "RegisterReplicationInstance"sv;
|
||||
case NotificationCode::REGISTER_COORDINATOR_SERVER:
|
||||
return "RegisterCoordinatorServer"sv;
|
||||
case NotificationCode::ADD_COORDINATOR_INSTANCE:
|
||||
return "AddCoordinatorInstance"sv;
|
||||
case NotificationCode::UNREGISTER_INSTANCE:
|
||||
|
||||
@@ -43,7 +43,7 @@ enum class NotificationCode : uint8_t {
|
||||
REPLICA_PORT_WARNING,
|
||||
REGISTER_REPLICA,
|
||||
#ifdef MG_ENTERPRISE
|
||||
REGISTER_REPLICATION_INSTANCE,
|
||||
REGISTER_COORDINATOR_SERVER, // TODO: (andi) What is this?
|
||||
ADD_COORDINATOR_INSTANCE,
|
||||
UNREGISTER_INSTANCE,
|
||||
#endif
|
||||
|
||||
@@ -16,15 +16,28 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
namespace memgraph::replication_coordination_glue {
|
||||
|
||||
enum class ReplicationMode : std::uint8_t { SYNC, ASYNC };
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ReplicationMode, {
|
||||
{ReplicationMode::SYNC, "sync"},
|
||||
{ReplicationMode::ASYNC, "async"},
|
||||
})
|
||||
inline auto ReplicationModeToString(ReplicationMode mode) -> std::string {
|
||||
switch (mode) {
|
||||
case ReplicationMode::SYNC:
|
||||
return "SYNC";
|
||||
case ReplicationMode::ASYNC:
|
||||
return "ASYNC";
|
||||
}
|
||||
throw std::invalid_argument("Invalid replication mode");
|
||||
}
|
||||
|
||||
inline auto ReplicationModeFromString(std::string_view mode) -> ReplicationMode {
|
||||
if (mode == "SYNC") {
|
||||
return ReplicationMode::SYNC;
|
||||
}
|
||||
if (mode == "ASYNC") {
|
||||
return ReplicationMode::ASYNC;
|
||||
}
|
||||
throw std::invalid_argument("Invalid replication mode");
|
||||
}
|
||||
|
||||
} // namespace memgraph::replication_coordination_glue
|
||||
|
||||
@@ -12,14 +12,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
namespace memgraph::replication_coordination_glue {
|
||||
|
||||
// TODO: figure out a way of ensuring that usage of this type is never uninitialed/defaulted incorrectly to MAIN
|
||||
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ReplicationRole, {{ReplicationRole::MAIN, "main"}, {ReplicationRole::REPLICA, "replica"}})
|
||||
|
||||
} // namespace memgraph::replication_coordination_glue
|
||||
|
||||
@@ -210,13 +210,8 @@ struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(*instance_client_ptr, main_uuid);
|
||||
client->Start(storage, std::move(db_acc));
|
||||
bool const success = std::invoke([state = client->State()]() {
|
||||
// We force sync replicas in other situation
|
||||
if (state == storage::replication::ReplicaState::DIVERGED_FROM_MAIN) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
return FLAGS_coordinator_server_port != 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -271,7 +271,8 @@ auto ReplicationHandler::GetDatabasesHistories() -> replication_coordination_glu
|
||||
dbms_handler_.ForEach([&results](memgraph::dbms::DatabaseAccess db_acc) {
|
||||
auto &repl_storage_state = db_acc->storage()->repl_storage_state_;
|
||||
|
||||
std::vector<std::pair<std::string, uint64_t>> history = utils::fmap(repl_storage_state.history);
|
||||
std::vector<std::pair<std::string, uint64_t>> history = utils::fmap(
|
||||
[](const auto &elem) { return std::make_pair(elem.first, elem.second); }, repl_storage_state.history);
|
||||
|
||||
history.emplace_back(std::string(repl_storage_state.epoch_.id()), repl_storage_state.last_commit_timestamp_.load());
|
||||
replication_coordination_glue::DatabaseHistory repl{
|
||||
|
||||
@@ -358,6 +358,7 @@ std::optional<RecoveryInfo> Recovery::RecoverData(std::string *uuid, Replication
|
||||
spdlog::warn(utils::MessageWithLink("No snapshot or WAL file found.", "https://memgr.ph/durability"));
|
||||
return std::nullopt;
|
||||
}
|
||||
// TODO(antoniofilipovic) What is the logic here?
|
||||
std::sort(wal_files.begin(), wal_files.end());
|
||||
// UUID used for durability is the UUID of the last WAL file.
|
||||
// Same for the epoch id.
|
||||
@@ -436,13 +437,17 @@ std::optional<RecoveryInfo> Recovery::RecoverData(std::string *uuid, Replication
|
||||
last_loaded_timestamp.emplace(recovery_info.next_timestamp - 1);
|
||||
}
|
||||
|
||||
bool epoch_history_empty = epoch_history->empty();
|
||||
bool epoch_not_recorded = !epoch_history_empty && epoch_history->back().first != wal_file.epoch_id;
|
||||
auto last_loaded_timestamp_value = last_loaded_timestamp.value_or(0);
|
||||
if (epoch_history->empty() || epoch_history->back().first != wal_file.epoch_id) {
|
||||
// no history or new epoch, add it
|
||||
epoch_history->emplace_back(wal_file.epoch_id, last_loaded_timestamp_value);
|
||||
repl_storage_state.epoch_.SetEpoch(wal_file.epoch_id);
|
||||
} else if (epoch_history->back().second < last_loaded_timestamp_value) {
|
||||
// existing epoch, update with newer timestamp
|
||||
|
||||
if (epoch_history_empty || epoch_not_recorded) {
|
||||
epoch_history->emplace_back(std::string(wal_file.epoch_id), last_loaded_timestamp_value);
|
||||
}
|
||||
|
||||
auto last_epoch_updated = !epoch_history_empty && epoch_history->back().first == wal_file.epoch_id &&
|
||||
epoch_history->back().second < last_loaded_timestamp_value;
|
||||
if (last_epoch_updated) {
|
||||
epoch_history->back().second = last_loaded_timestamp_value;
|
||||
}
|
||||
|
||||
@@ -464,11 +469,11 @@ std::optional<RecoveryInfo> Recovery::RecoverData(std::string *uuid, Replication
|
||||
|
||||
memgraph::metrics::Measure(memgraph::metrics::SnapshotRecoveryLatency_us,
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(timer.Elapsed()).count());
|
||||
spdlog::trace("Set epoch id: {} with commit timestamp {}", std::string(repl_storage_state.epoch_.id()),
|
||||
repl_storage_state.last_commit_timestamp_);
|
||||
spdlog::info("Set epoch id: {} with commit timestamp {}", std::string(repl_storage_state.epoch_.id()),
|
||||
repl_storage_state.last_commit_timestamp_);
|
||||
|
||||
std::for_each(repl_storage_state.history.begin(), repl_storage_state.history.end(), [](auto &history) {
|
||||
spdlog::trace("epoch id: {} with commit timestamp {}", std::string(history.first), history.second);
|
||||
spdlog::info("epoch id: {} with commit timestamp {}", std::string(history.first), history.second);
|
||||
});
|
||||
return recovery_info;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ InMemoryStorage::InMemoryStorage(Config config)
|
||||
timestamp_ = std::max(timestamp_, info->next_timestamp);
|
||||
if (info->last_commit_timestamp) {
|
||||
repl_storage_state_.last_commit_timestamp_ = *info->last_commit_timestamp;
|
||||
spdlog::trace("Recovering last commit timestamp {}", *info->last_commit_timestamp);
|
||||
spdlog::info("Recovering last commit timestamp {}", *info->last_commit_timestamp);
|
||||
}
|
||||
}
|
||||
} else if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED ||
|
||||
|
||||
@@ -76,37 +76,13 @@ void ReplicationStorageClient::UpdateReplicaState(Storage *storage, DatabaseAcce
|
||||
}
|
||||
}
|
||||
if (branching_point) {
|
||||
auto replica_state = replica_state_.Lock();
|
||||
if (*replica_state == replication::ReplicaState::DIVERGED_FROM_MAIN) {
|
||||
return;
|
||||
}
|
||||
*replica_state = replication::ReplicaState::DIVERGED_FROM_MAIN;
|
||||
|
||||
auto log_error = [client_name = client_.name_]() {
|
||||
spdlog::error(
|
||||
"You cannot register Replica {} to this Main because at one point "
|
||||
"Replica {} acted as the Main instance. Both the Main and Replica {} "
|
||||
"now hold unique data. Please resolve data conflicts and start the "
|
||||
"replication on a clean instance.",
|
||||
client_name, client_name, client_name);
|
||||
};
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (!FLAGS_coordinator_server_port) {
|
||||
log_error();
|
||||
return;
|
||||
}
|
||||
client_.thread_pool_.AddTask([storage, gk = std::move(db_acc), this] {
|
||||
const auto [success, timestamp] = this->ForceResetStorage(storage);
|
||||
if (success) {
|
||||
spdlog::info("Successfully reset storage of REPLICA {} to timestamp {}.", client_.name_, timestamp);
|
||||
return;
|
||||
}
|
||||
spdlog::error("You cannot register REPLICA {} to this MAIN because MAIN couldn't reset REPLICA's storage.",
|
||||
client_.name_);
|
||||
});
|
||||
#else
|
||||
log_error();
|
||||
#endif
|
||||
spdlog::error(
|
||||
"You cannot register Replica {} to this Main because at one point "
|
||||
"Replica {} acted as the Main instance. Both the Main and Replica {} "
|
||||
"now hold unique data. Please resolve data conflicts and start the "
|
||||
"replication on a clean instance.",
|
||||
client_.name_, client_.name_, client_.name_);
|
||||
replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::DIVERGED_FROM_MAIN; });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -357,21 +333,6 @@ void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph:
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<bool, uint64_t> ReplicationStorageClient::ForceResetStorage(memgraph::storage::Storage *storage) {
|
||||
utils::OnScopeExit set_to_maybe_behind{
|
||||
[this]() { replica_state_.WithLock([](auto &state) { state = replication::ReplicaState::MAYBE_BEHIND; }); }};
|
||||
try {
|
||||
auto stream{client_.rpc_client_.Stream<replication::ForceResetStorageRpc>(main_uuid_, storage->uuid())};
|
||||
const auto res = stream.AwaitResponse();
|
||||
return std::pair{res.success, res.current_commit_timestamp};
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Couldn't ForceReset data to {}.", client_.name_, "https://memgr.ph/replication"));
|
||||
}
|
||||
|
||||
return {false, 0};
|
||||
}
|
||||
|
||||
////// ReplicaStream //////
|
||||
ReplicaStream::ReplicaStream(Storage *storage, rpc::Client &rpc_client, const uint64_t current_seq_num,
|
||||
utils::UUID main_uuid)
|
||||
|
||||
@@ -188,13 +188,6 @@ class ReplicationStorageClient {
|
||||
*/
|
||||
void UpdateReplicaState(Storage *storage, DatabaseAccessProtector db_acc);
|
||||
|
||||
/**
|
||||
* @brief Forcefully reset storage to as it is when started from scratch.
|
||||
*
|
||||
* @param storage pointer to the storage associated with the client
|
||||
*/
|
||||
std::pair<bool, uint64_t> ForceResetStorage(Storage *storage);
|
||||
|
||||
void LogRpcFailure();
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,19 +59,6 @@ void TimestampRes::Save(const TimestampRes &self, memgraph::slk::Builder *builde
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void TimestampRes::Load(TimestampRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
|
||||
void ForceResetStorageReq::Save(const ForceResetStorageReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void ForceResetStorageReq::Load(ForceResetStorageReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void ForceResetStorageRes::Save(const ForceResetStorageRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void ForceResetStorageRes::Load(ForceResetStorageRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
} // namespace storage::replication
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::AppendDeltasReq::kType{utils::TypeId::REP_APPEND_DELTAS_REQ,
|
||||
@@ -110,12 +97,6 @@ constexpr utils::TypeInfo storage::replication::TimestampReq::kType{utils::TypeI
|
||||
constexpr utils::TypeInfo storage::replication::TimestampRes::kType{utils::TypeId::REP_TIMESTAMP_RES, "TimestampRes",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::ForceResetStorageReq::kType{utils::TypeId::REP_FORCE_RESET_STORAGE_REQ,
|
||||
"ForceResetStorageReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::ForceResetStorageRes::kType{utils::TypeId::REP_FORCE_RESET_STORAGE_RES,
|
||||
"ForceResetStorageRes", nullptr};
|
||||
|
||||
// Autogenerated SLK serialization code
|
||||
namespace slk {
|
||||
// Serialize code for TimestampRes
|
||||
@@ -274,30 +255,6 @@ void Load(memgraph::storage::replication::AppendDeltasReq *self, memgraph::slk::
|
||||
memgraph::slk::Load(&self->seq_num, reader);
|
||||
}
|
||||
|
||||
// Serialize code for ForceResetStorageReq
|
||||
|
||||
void Save(const memgraph::storage::replication::ForceResetStorageReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.main_uuid, builder);
|
||||
memgraph::slk::Save(self.db_uuid, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::ForceResetStorageReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->main_uuid, reader);
|
||||
memgraph::slk::Load(&self->db_uuid, reader);
|
||||
}
|
||||
|
||||
// Serialize code for ForceResetStorageRes
|
||||
|
||||
void Save(const memgraph::storage::replication::ForceResetStorageRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
memgraph::slk::Save(self.current_commit_timestamp, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::ForceResetStorageRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
memgraph::slk::Load(&self->current_commit_timestamp, reader);
|
||||
}
|
||||
|
||||
// Serialize SalientConfig
|
||||
|
||||
void Save(const memgraph::storage::SalientConfig &self, memgraph::slk::Builder *builder) {
|
||||
|
||||
@@ -210,36 +210,6 @@ struct TimestampRes {
|
||||
|
||||
using TimestampRpc = rpc::RequestResponse<TimestampReq, TimestampRes>;
|
||||
|
||||
struct ForceResetStorageReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(ForceResetStorageReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const ForceResetStorageReq &self, memgraph::slk::Builder *builder);
|
||||
ForceResetStorageReq() = default;
|
||||
explicit ForceResetStorageReq(const utils::UUID &main_uuid, const utils::UUID &db_uuid)
|
||||
: main_uuid{main_uuid}, db_uuid{db_uuid} {}
|
||||
|
||||
utils::UUID main_uuid;
|
||||
utils::UUID db_uuid;
|
||||
};
|
||||
|
||||
struct ForceResetStorageRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(ForceResetStorageRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const ForceResetStorageRes &self, memgraph::slk::Builder *builder);
|
||||
ForceResetStorageRes() = default;
|
||||
ForceResetStorageRes(bool success, uint64_t current_commit_timestamp)
|
||||
: success(success), current_commit_timestamp(current_commit_timestamp) {}
|
||||
|
||||
bool success;
|
||||
uint64_t current_commit_timestamp;
|
||||
};
|
||||
|
||||
using ForceResetStorageRpc = rpc::RequestResponse<ForceResetStorageReq, ForceResetStorageRes>;
|
||||
|
||||
} // namespace memgraph::storage::replication
|
||||
|
||||
// SLK serialization declarations
|
||||
@@ -297,12 +267,4 @@ void Save(const memgraph::storage::SalientConfig &self, memgraph::slk::Builder *
|
||||
|
||||
void Load(memgraph::storage::SalientConfig *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::ForceResetStorageReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::ForceResetStorageReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::ForceResetStorageRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::ForceResetStorageRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
namespace memgraph::utils {
|
||||
|
||||
template <template <typename, typename...> class Container, typename T, typename Allocator = std::allocator<T>,
|
||||
typename F = std::identity, typename R = std::decay_t<std::invoke_result_t<F, T>>>
|
||||
typename F, typename R = std::invoke_result_t<F, T>>
|
||||
requires ranges::range<Container<T, Allocator>> &&
|
||||
(!std::same_as<Container<T, Allocator>, std::string>)auto fmap(const Container<T, Allocator> &v, F &&f = {})
|
||||
(!std::same_as<Container<T, Allocator>, std::string>)auto fmap(F &&f, const Container<T, Allocator> &v)
|
||||
-> std::vector<R> {
|
||||
return v | ranges::views::transform(std::forward<F>(f)) | ranges::to<std::vector<R>>();
|
||||
}
|
||||
|
||||
@@ -99,8 +99,6 @@ enum class TypeId : uint64_t {
|
||||
REP_DROP_AUTH_DATA_RES,
|
||||
REP_TRY_SET_MAIN_UUID_REQ,
|
||||
REP_TRY_SET_MAIN_UUID_RES,
|
||||
REP_FORCE_RESET_STORAGE_REQ,
|
||||
REP_FORCE_RESET_STORAGE_RES,
|
||||
|
||||
// Coordinator
|
||||
COORD_FAILOVER_REQ,
|
||||
|
||||
@@ -13,6 +13,7 @@ import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from time import sleep
|
||||
|
||||
import interactive_mg_runner
|
||||
import pytest
|
||||
@@ -122,9 +123,19 @@ MEMGRAPH_INSTANCES_DESCRIPTION = {
|
||||
|
||||
|
||||
def test_distributed_automatic_failover():
|
||||
# Goal of this test is to check that coordination works if one instance fails
|
||||
# with multiple coordinators
|
||||
|
||||
# 1. Start all manually
|
||||
# 2. Everything is set up on main
|
||||
# 3. MAIN dies
|
||||
# 4. New main elected
|
||||
|
||||
# 1
|
||||
safe_execute(shutil.rmtree, TEMP_DIR)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
# 2
|
||||
main_cursor = connect(host="localhost", port=7689).cursor()
|
||||
expected_data_on_main = [
|
||||
(
|
||||
@@ -148,8 +159,10 @@ def test_distributed_automatic_failover():
|
||||
|
||||
mg_sleep_and_assert_collection(expected_data_on_main, retrieve_data_show_replicas)
|
||||
|
||||
# 3
|
||||
interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
|
||||
# 4
|
||||
coord_cursor = connect(host="localhost", port=7692).cursor()
|
||||
|
||||
def retrieve_data_show_repl_cluster():
|
||||
@@ -210,11 +223,21 @@ def test_distributed_automatic_failover():
|
||||
|
||||
|
||||
def test_distributed_automatic_failover_after_coord_dies():
|
||||
# Goal of this test is to check if main and coordinator die at same time in the beginning that
|
||||
# everything works fine
|
||||
# 1. Start all manually
|
||||
# 2. Coordinator dies
|
||||
# 3. MAIN dies
|
||||
# 4.
|
||||
safe_execute(shutil.rmtree, TEMP_DIR)
|
||||
|
||||
# 1
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
# 2
|
||||
interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "coordinator_3")
|
||||
|
||||
# 3
|
||||
interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
|
||||
coord_cursor_1 = connect(host="localhost", port=7690).cursor()
|
||||
@@ -243,7 +266,7 @@ def test_distributed_automatic_failover_after_coord_dies():
|
||||
("coordinator_3", "127.0.0.1:10113", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "", "unknown", "main"),
|
||||
("instance_2", "", "", "unknown", "replica"),
|
||||
("instance_3", "", "", "unknown", "main"), # TODO: (andi) Will become unknown.
|
||||
("instance_3", "", "", "unknown", "main"),
|
||||
]
|
||||
mg_sleep_and_assert_any_function(leader_data, [show_instances_coord1, show_instances_coord2])
|
||||
mg_sleep_and_assert_any_function(follower_data, [show_instances_coord1, show_instances_coord2])
|
||||
@@ -292,5 +315,332 @@ def test_distributed_automatic_failover_after_coord_dies():
|
||||
mg_sleep_and_assert_collection(expected_data_on_new_main_old_alive, retrieve_data_show_replicas)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data_recovery", ["false"])
|
||||
def test_distributed_coordinators_work_partial_failover(data_recovery):
|
||||
# Goal of this test is to check that correct MAIN instance is chosen
|
||||
# if coordinator dies while failover is being done
|
||||
# 1. We start all replicas, main and 4 coordinators manually
|
||||
# 2. We check that main has correct state
|
||||
# 3. Create initial data on MAIN
|
||||
# 4. Expect data to be copied on all replicas
|
||||
# 5. Kill MAIN: instance 3
|
||||
|
||||
# 6. Failover should succeed to instance 1 -> SHOW ROLE -> MAIN
|
||||
# 7. Kill 2 coordinators (1 and 2) as soon as instance becomes MAIN
|
||||
# 8. coord 4 become follower (2 coords dead, no leader)
|
||||
# 8. instance_1 writes (MAIN, successfully)
|
||||
# 9. Kill coordinator 4
|
||||
# 10. Connect to coordinator 3
|
||||
# 11. Start coordinator_1 and coordinator_2 (coord 3 probably leader)
|
||||
# 12. Failover again
|
||||
# 13. Main can't write to replicas anymore, coordinator can't progress
|
||||
|
||||
temp_dir = tempfile.TemporaryDirectory().name
|
||||
|
||||
MEMGRAPH_INNER_INSTANCES_DESCRIPTION = {
|
||||
"instance_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10011",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
f"--data-recovery-on-startup={data_recovery}",
|
||||
"--storage-recover-on-startup=false",
|
||||
],
|
||||
"log_file": "instance_1.log",
|
||||
"data_directory": f"{temp_dir}/instance_1",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"instance_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7689",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10012",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
f"--data-recovery-on-startup={data_recovery}",
|
||||
"--storage-recover-on-startup=false",
|
||||
],
|
||||
"log_file": "instance_2.log",
|
||||
"data_directory": f"{temp_dir}/instance_2",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"instance_3": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7687",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10013",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
f"{data_recovery}",
|
||||
"--storage-recover-on-startup=false",
|
||||
],
|
||||
"log_file": "instance_3.log",
|
||||
"data_directory": f"{temp_dir}/instance_3",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7690",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=1",
|
||||
"--raft-server-port=10111",
|
||||
],
|
||||
"log_file": "coordinator1.log",
|
||||
"data_directory": f"{temp_dir}/coordinator_1",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7691",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=2",
|
||||
"--raft-server-port=10112",
|
||||
],
|
||||
"log_file": "coordinator2.log",
|
||||
"data_directory": f"{temp_dir}/coordinator_2",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator_3": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7692",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=3",
|
||||
"--raft-server-port=10113",
|
||||
],
|
||||
"log_file": "coordinator3.log",
|
||||
"data_directory": f"{temp_dir}/coordinator_3",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator_4": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7693",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=4",
|
||||
"--raft-server-port=10114",
|
||||
],
|
||||
"log_file": "coordinator4.log",
|
||||
"data_directory": f"{temp_dir}/coordinator_4",
|
||||
"setup_queries": [],
|
||||
},
|
||||
}
|
||||
|
||||
# 1
|
||||
|
||||
interactive_mg_runner.start_all_except(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, {"coordinator_4"})
|
||||
|
||||
interactive_mg_runner.start(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "coordinator_4")
|
||||
|
||||
coord_cursor = connect(host="localhost", port=7693).cursor()
|
||||
|
||||
for query in [
|
||||
"ADD COORDINATOR 1 ON '127.0.0.1:10111';",
|
||||
"ADD COORDINATOR 2 ON '127.0.0.1:10112';",
|
||||
"ADD COORDINATOR 3 ON '127.0.0.1:10113';",
|
||||
]:
|
||||
sleep(1)
|
||||
execute_and_fetch_all(coord_cursor, query)
|
||||
|
||||
for query in [
|
||||
"REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003';",
|
||||
"REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001';",
|
||||
"REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002';",
|
||||
"SET INSTANCE instance_3 TO MAIN;",
|
||||
]:
|
||||
execute_and_fetch_all(coord_cursor, query)
|
||||
|
||||
# 2
|
||||
|
||||
main_cursor = connect(host="localhost", port=7687).cursor()
|
||||
expected_data_on_main = [
|
||||
(
|
||||
"instance_1",
|
||||
"127.0.0.1:10001",
|
||||
"sync",
|
||||
{"behind": None, "status": "ready", "ts": 0},
|
||||
{"memgraph": {"behind": 0, "status": "ready", "ts": 0}},
|
||||
),
|
||||
(
|
||||
"instance_2",
|
||||
"127.0.0.1:10002",
|
||||
"sync",
|
||||
{"behind": None, "status": "ready", "ts": 0},
|
||||
{"memgraph": {"behind": 0, "status": "ready", "ts": 0}},
|
||||
),
|
||||
]
|
||||
|
||||
main_cursor = connect(host="localhost", port=7687).cursor()
|
||||
|
||||
def retrieve_data_show_replicas():
|
||||
return sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS;")))
|
||||
|
||||
mg_sleep_and_assert_collection(expected_data_on_main, retrieve_data_show_replicas)
|
||||
|
||||
coord_cursor = connect(host="localhost", port=7693).cursor()
|
||||
|
||||
def retrieve_data_show_instances_main_coord():
|
||||
return sorted(list(execute_and_fetch_all(coord_cursor, "SHOW INSTANCES;")))
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("coordinator_2", "127.0.0.1:10112", "", "unknown", "coordinator"),
|
||||
("coordinator_3", "127.0.0.1:10113", "", "unknown", "coordinator"),
|
||||
("coordinator_4", "127.0.0.1:10114", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", "up", "replica"),
|
||||
("instance_2", "", "127.0.0.1:10012", "up", "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", "up", "main"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances_main_coord)
|
||||
|
||||
# 3
|
||||
|
||||
execute_and_fetch_all(main_cursor, "CREATE (:Epoch1Vertex {prop:1});")
|
||||
execute_and_fetch_all(main_cursor, "CREATE (:Epoch1Vertex {prop:2});")
|
||||
|
||||
# 4
|
||||
instance_1_cursor = connect(host="localhost", port=7688).cursor()
|
||||
instance_2_cursor = connect(host="localhost", port=7689).cursor()
|
||||
|
||||
assert execute_and_fetch_all(instance_1_cursor, "MATCH (n) RETURN count(n);")[0][0] == 2
|
||||
assert execute_and_fetch_all(instance_2_cursor, "MATCH (n) RETURN count(n);")[0][0] == 2
|
||||
|
||||
# 5
|
||||
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "instance_3")
|
||||
# 6
|
||||
|
||||
sleep(4.5)
|
||||
max_tries = 100
|
||||
last_role = "replica"
|
||||
while max_tries:
|
||||
max_tries -= 1
|
||||
last_role = execute_and_fetch_all(instance_1_cursor, "SHOW REPLICATION ROLE;")[0][0]
|
||||
if "main" == last_role:
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "coordinator_1")
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "coordinator_2")
|
||||
break
|
||||
sleep(0.1)
|
||||
assert max_tries > 0 or last_role == "main"
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("coordinator_2", "127.0.0.1:10112", "", "unknown", "coordinator"),
|
||||
("coordinator_3", "127.0.0.1:10113", "", "unknown", "coordinator"),
|
||||
("coordinator_4", "127.0.0.1:10114", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", "unknown", "main"), # TODO although above we see it is MAIN
|
||||
("instance_2", "", "127.0.0.1:10012", "unknown", "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", "unknown", "main"),
|
||||
]
|
||||
|
||||
def retrieve_data_show_instances():
|
||||
return sorted(list(execute_and_fetch_all(coord_cursor, "SHOW INSTANCES;")))
|
||||
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances)
|
||||
|
||||
# 7
|
||||
def retrieve_role():
|
||||
return execute_and_fetch_all(instance_1_cursor, "SHOW REPLICATION ROLE;")[0]
|
||||
|
||||
mg_sleep_and_assert("MAIN", retrieve_role)
|
||||
|
||||
# 8
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
execute_and_fetch_all(instance_1_cursor, "CREATE (:Epoch2Vertex {prop:1});")
|
||||
assert "At least one SYNC replica has not confirmed committing last transaction." in str(e.value)
|
||||
|
||||
def get_vertex_count_instance_2():
|
||||
return execute_and_fetch_all(instance_2_cursor, "MATCH (n) RETURN count(n)")[0][0]
|
||||
|
||||
mg_sleep_and_assert(3, get_vertex_count_instance_2)
|
||||
|
||||
assert execute_and_fetch_all(instance_1_cursor, "MATCH (n) RETURN count(n);")[0][0] == 3
|
||||
|
||||
# 9
|
||||
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "coordinator_4")
|
||||
|
||||
# 10
|
||||
|
||||
coord_3_cursor = connect(port=7692, host="localhost").cursor()
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("coordinator_2", "127.0.0.1:10112", "", "unknown", "coordinator"),
|
||||
("coordinator_3", "127.0.0.1:10113", "", "unknown", "coordinator"),
|
||||
("coordinator_4", "127.0.0.1:10114", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", "unknown", "replica"), # TODO: bug this is main
|
||||
("instance_2", "", "127.0.0.1:10012", "unknown", "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", "unknown", "replica"),
|
||||
]
|
||||
|
||||
def retrieve_data_show_instances_new_leader():
|
||||
return sorted(list(execute_and_fetch_all(coord_3_cursor, "SHOW INSTANCES;")))
|
||||
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances_new_leader)
|
||||
|
||||
# 11
|
||||
|
||||
interactive_mg_runner.start(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "coordinator_1")
|
||||
interactive_mg_runner.start(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "coordinator_2")
|
||||
|
||||
# 12
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("coordinator_2", "127.0.0.1:10112", "", "unknown", "coordinator"),
|
||||
("coordinator_3", "127.0.0.1:10113", "", "unknown", "coordinator"),
|
||||
("coordinator_4", "127.0.0.1:10114", "", "unknown", "coordinator"),
|
||||
(
|
||||
"instance_1",
|
||||
"",
|
||||
"127.0.0.1:10011",
|
||||
"up",
|
||||
"replica",
|
||||
), # TODO: bug this is main, this might crash instance, because we will maybe execute replica callback on MAIN, which won't work
|
||||
("instance_2", "", "127.0.0.1:10012", "up", "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", "down", "unknown"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances_new_leader)
|
||||
|
||||
import time
|
||||
|
||||
time.sleep(5) # failover
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
execute_and_fetch_all(instance_1_cursor, "CREATE (:Epoch2Vertex {prop:2});")
|
||||
assert "At least one SYNC replica has not confirmed committing last transaction." in str(e.value)
|
||||
|
||||
def get_vertex_count_instance_2():
|
||||
return execute_and_fetch_all(instance_2_cursor, "MATCH (n) RETURN count(n)")[0][0]
|
||||
|
||||
mg_sleep_and_assert(4, get_vertex_count_instance_2) # MAIN will not be able to write to it anymore
|
||||
|
||||
# 13
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-k", "test_distributed_coordinators_work_partial_failover", "-vv"]))
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
|
||||
@@ -596,8 +596,9 @@ def test_replication_works_on_failover_replica_2_epochs_more_commits_away(data_r
|
||||
mg_sleep_and_assert(5, get_vertex_count)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data_recovery", ["true"])
|
||||
@pytest.mark.parametrize("data_recovery", ["false"])
|
||||
def test_replication_forcefully_works_on_failover_replica_misses_epoch(data_recovery):
|
||||
# TODO(antoniofilipovic) Test should pass when logic is added
|
||||
# Goal of this test is to check the replication works forcefully if replica misses epoch
|
||||
# 1. We start all replicas, main and coordinator manually
|
||||
# 2. We check that main has correct state
|
||||
@@ -605,9 +606,9 @@ def test_replication_forcefully_works_on_failover_replica_misses_epoch(data_reco
|
||||
# 4. Expect data to be copied on all replicas
|
||||
# 5. Kill instance_1 ( this one will miss complete epoch)
|
||||
# 6. Kill main (instance_3)
|
||||
# 7. Instance_2
|
||||
# 8. Instance_2 commits
|
||||
# 9. Instance_2 down
|
||||
# 7. Instance_2 or instance_4 new main
|
||||
# 8. New main commits
|
||||
# 9. Instance_2 down (not main)
|
||||
# 10. instance_4 down
|
||||
# 11. Instance 1 up (missed epoch)
|
||||
# 12 Instance 1 new main
|
||||
@@ -616,272 +617,7 @@ def test_replication_forcefully_works_on_failover_replica_misses_epoch(data_reco
|
||||
|
||||
temp_dir = tempfile.TemporaryDirectory().name
|
||||
|
||||
MEMGRAPH_INNER_INSTANCES_DESCRIPTION = {
|
||||
"instance_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10011",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
f"--data-recovery-on-startup={data_recovery}",
|
||||
"--storage-recover-on-startup=false",
|
||||
],
|
||||
"log_file": "instance_1.log",
|
||||
"data_directory": f"{temp_dir}/instance_1",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"instance_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7689",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10012",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
f"--data-recovery-on-startup={data_recovery}",
|
||||
"--storage-recover-on-startup=false",
|
||||
],
|
||||
"log_file": "instance_2.log",
|
||||
"data_directory": f"{temp_dir}/instance_2",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"instance_3": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7687",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10013",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
f"{data_recovery}",
|
||||
"--storage-recover-on-startup=false",
|
||||
],
|
||||
"log_file": "instance_3.log",
|
||||
"data_directory": f"{temp_dir}/instance_3",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"instance_4": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7691",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10014",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
f"{data_recovery}",
|
||||
"--storage-recover-on-startup=false",
|
||||
],
|
||||
"log_file": "instance_4.log",
|
||||
"data_directory": f"{temp_dir}/instance_4",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator": {
|
||||
"args": [
|
||||
"--experimental-enabled=high-availability",
|
||||
"--bolt-port",
|
||||
"7690",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=1",
|
||||
"--raft-server-port=10111",
|
||||
],
|
||||
"log_file": "coordinator.log",
|
||||
"setup_queries": [
|
||||
"REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001';",
|
||||
"REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002';",
|
||||
"REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003';",
|
||||
"REGISTER INSTANCE instance_4 ON '127.0.0.1:10014' WITH '127.0.0.1:10004';",
|
||||
"SET INSTANCE instance_3 TO MAIN",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 1
|
||||
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INNER_INSTANCES_DESCRIPTION)
|
||||
|
||||
# 2
|
||||
|
||||
main_cursor = connect(host="localhost", port=7687).cursor()
|
||||
expected_data_on_main = [
|
||||
(
|
||||
"instance_1",
|
||||
"127.0.0.1:10001",
|
||||
"sync",
|
||||
{"behind": None, "status": "ready", "ts": 0},
|
||||
{"memgraph": {"behind": 0, "status": "ready", "ts": 0}},
|
||||
),
|
||||
(
|
||||
"instance_2",
|
||||
"127.0.0.1:10002",
|
||||
"sync",
|
||||
{"behind": None, "status": "ready", "ts": 0},
|
||||
{"memgraph": {"behind": 0, "status": "ready", "ts": 0}},
|
||||
),
|
||||
(
|
||||
"instance_4",
|
||||
"127.0.0.1:10004",
|
||||
"sync",
|
||||
{"behind": None, "status": "ready", "ts": 0},
|
||||
{"memgraph": {"behind": 0, "status": "ready", "ts": 0}},
|
||||
),
|
||||
]
|
||||
|
||||
main_cursor = connect(host="localhost", port=7687).cursor()
|
||||
|
||||
def retrieve_data_show_replicas():
|
||||
return sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS;")))
|
||||
|
||||
mg_sleep_and_assert_collection(expected_data_on_main, retrieve_data_show_replicas)
|
||||
|
||||
coord_cursor = connect(host="localhost", port=7690).cursor()
|
||||
|
||||
def retrieve_data_show_instances():
|
||||
return sorted(list(execute_and_fetch_all(coord_cursor, "SHOW INSTANCES;")))
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", "up", "replica"),
|
||||
("instance_2", "", "127.0.0.1:10012", "up", "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", "up", "main"),
|
||||
("instance_4", "", "127.0.0.1:10014", "up", "replica"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances)
|
||||
|
||||
# 3
|
||||
|
||||
execute_and_fetch_all(main_cursor, "CREATE (:Epoch1Vertex {prop:1});")
|
||||
execute_and_fetch_all(main_cursor, "CREATE (:Epoch1Vertex {prop:2});")
|
||||
|
||||
# 4
|
||||
instance_1_cursor = connect(host="localhost", port=7688).cursor()
|
||||
instance_2_cursor = connect(host="localhost", port=7689).cursor()
|
||||
instance_4_cursor = connect(host="localhost", port=7691).cursor()
|
||||
|
||||
assert execute_and_fetch_all(instance_1_cursor, "MATCH (n) RETURN count(n);")[0][0] == 2
|
||||
assert execute_and_fetch_all(instance_2_cursor, "MATCH (n) RETURN count(n);")[0][0] == 2
|
||||
assert execute_and_fetch_all(instance_4_cursor, "MATCH (n) RETURN count(n);")[0][0] == 2
|
||||
|
||||
# 5
|
||||
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "instance_1")
|
||||
|
||||
# 6
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "instance_3")
|
||||
|
||||
# 7
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", "down", "unknown"),
|
||||
("instance_2", "", "127.0.0.1:10012", "up", "main"),
|
||||
("instance_3", "", "127.0.0.1:10013", "down", "unknown"),
|
||||
("instance_4", "", "127.0.0.1:10014", "up", "replica"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances)
|
||||
|
||||
# 8
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
execute_and_fetch_all(instance_2_cursor, "CREATE (:Epoch2Vertex {prop:1});")
|
||||
assert "At least one SYNC replica has not confirmed committing last transaction." in str(e.value)
|
||||
|
||||
def get_vertex_count():
|
||||
return execute_and_fetch_all(instance_4_cursor, "MATCH (n) RETURN count(n)")[0][0]
|
||||
|
||||
mg_sleep_and_assert(3, get_vertex_count)
|
||||
|
||||
assert execute_and_fetch_all(instance_4_cursor, "MATCH (n) RETURN count(n);")[0][0] == 3
|
||||
|
||||
# 9
|
||||
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "instance_2")
|
||||
|
||||
# 10
|
||||
|
||||
interactive_mg_runner.kill(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "instance_4")
|
||||
|
||||
# 11
|
||||
|
||||
interactive_mg_runner.start(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "instance_1")
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", "up", "main"),
|
||||
("instance_2", "", "127.0.0.1:10012", "down", "unknown"),
|
||||
("instance_3", "", "127.0.0.1:10013", "down", "unknown"),
|
||||
("instance_4", "", "127.0.0.1:10014", "down", "unknown"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances)
|
||||
|
||||
# 12
|
||||
|
||||
interactive_mg_runner.start(MEMGRAPH_INNER_INSTANCES_DESCRIPTION, "instance_2")
|
||||
|
||||
# 13
|
||||
|
||||
expected_data_on_coord = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", "unknown", "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", "up", "main"),
|
||||
("instance_2", "", "127.0.0.1:10012", "up", "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", "down", "unknown"),
|
||||
("instance_4", "", "127.0.0.1:10014", "down", "unknown"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_instances)
|
||||
|
||||
# 12
|
||||
instance_1_cursor = connect(host="localhost", port=7688).cursor()
|
||||
instance_2_cursor = connect(host="localhost", port=7689).cursor()
|
||||
|
||||
def get_vertex_count():
|
||||
return execute_and_fetch_all(instance_1_cursor, "MATCH (n) RETURN count(n)")[0][0]
|
||||
|
||||
mg_sleep_and_assert(2, get_vertex_count)
|
||||
|
||||
def get_vertex_count():
|
||||
return execute_and_fetch_all(instance_2_cursor, "MATCH (n) RETURN count(n)")[0][0]
|
||||
|
||||
mg_sleep_and_assert(2, get_vertex_count)
|
||||
|
||||
# 13
|
||||
with pytest.raises(Exception) as e:
|
||||
execute_and_fetch_all(instance_1_cursor, "CREATE (:Epoch3Vertex {prop:1});")
|
||||
assert "At least one SYNC replica has not confirmed committing last transaction." in str(e.value)
|
||||
|
||||
# 14
|
||||
|
||||
def get_vertex_objects_func_creator(cursor):
|
||||
def get_vertex_objects():
|
||||
return list(
|
||||
execute_and_fetch_all(
|
||||
cursor, "MATCH (n) " "WITH labels(n) as labels, properties(n) as props " "RETURN labels[0], props;"
|
||||
)
|
||||
)
|
||||
|
||||
return get_vertex_objects
|
||||
|
||||
vertex_objects = [("Epoch1Vertex", {"prop": 1}), ("Epoch1Vertex", {"prop": 2}), ("Epoch3Vertex", {"prop": 1})]
|
||||
|
||||
mg_sleep_and_assert_collection(vertex_objects, get_vertex_objects_func_creator(instance_1_cursor))
|
||||
|
||||
mg_sleep_and_assert_collection(vertex_objects, get_vertex_objects_func_creator(instance_2_cursor))
|
||||
|
||||
# 15
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data_recovery", ["false", "true"])
|
||||
|
||||
@@ -39,6 +39,7 @@ import tempfile
|
||||
import time
|
||||
from argparse import ArgumentParser
|
||||
from inspect import signature
|
||||
from typing import List, Set
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -214,6 +215,14 @@ def start_all(context, procdir="", keep_directories=True):
|
||||
start_instance(context, key, procdir)
|
||||
|
||||
|
||||
def start_all_except(context, exceptions: Set[str], procdir="", keep_directories=True):
|
||||
stop_all(keep_directories)
|
||||
for key, _ in context.items():
|
||||
if key in exceptions:
|
||||
continue
|
||||
start_instance(context, key, procdir)
|
||||
|
||||
|
||||
def start_all_keep_others(context, procdir="", keep_directories=True):
|
||||
for key, _ in context.items():
|
||||
start_instance(context, key, procdir)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -34,13 +34,12 @@ DEFINE_double(reads_duration_limit, 10.0, "How long should the client perform re
|
||||
namespace mg::e2e::replication {
|
||||
|
||||
auto ParseDatabaseEndpoints(const std::string &database_endpoints_str) {
|
||||
const auto db_endpoints_strs = memgraph::utils::SplitView(database_endpoints_str, ",");
|
||||
const auto db_endpoints_strs = memgraph::utils::Split(database_endpoints_str, ",");
|
||||
std::vector<memgraph::io::network::Endpoint> database_endpoints;
|
||||
for (const auto &db_endpoint_str : db_endpoints_strs) {
|
||||
const auto maybe_host_port = memgraph::io::network::Endpoint::ParseSocketOrAddress(db_endpoint_str, 7687);
|
||||
const auto maybe_host_port = memgraph::io::network::Endpoint::ParseSocketOrIpAddress(db_endpoint_str, 7687);
|
||||
MG_ASSERT(maybe_host_port);
|
||||
auto const [ip, port] = *maybe_host_port;
|
||||
database_endpoints.emplace_back(std::string(ip), port);
|
||||
database_endpoints.emplace_back(maybe_host_port->first, maybe_host_port->second);
|
||||
}
|
||||
return database_endpoints;
|
||||
}
|
||||
|
||||
@@ -438,17 +438,3 @@ add_unit_test(coordination_utils.cpp)
|
||||
target_link_libraries(${test_prefix}coordination_utils gflags mg-coordination mg-repl_coord_glue)
|
||||
target_include_directories(${test_prefix}coordination_utils PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
endif()
|
||||
|
||||
# Test Raft log serialization
|
||||
if(MG_ENTERPRISE)
|
||||
add_unit_test(raft_log_serialization.cpp)
|
||||
target_link_libraries(${test_prefix}raft_log_serialization gflags mg-coordination mg-repl_coord_glue)
|
||||
target_include_directories(${test_prefix}raft_log_serialization PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
endif()
|
||||
|
||||
# Test Raft log serialization
|
||||
if(MG_ENTERPRISE)
|
||||
add_unit_test(coordinator_cluster_state.cpp)
|
||||
target_link_libraries(${test_prefix}coordinator_cluster_state gflags mg-coordination mg-repl_coord_glue)
|
||||
target_include_directories(${test_prefix}coordinator_cluster_state PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
endif()
|
||||
|
||||
@@ -45,9 +45,11 @@ TEST_F(CoordinationUtils, MemgraphDbHistorySimple) {
|
||||
memgraph::utils::UUID db_uuid;
|
||||
std::string default_name = std::string(memgraph::dbms::kDefaultDB);
|
||||
|
||||
auto db_histories = memgraph::utils::fmap(histories, [](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
});
|
||||
auto db_histories = memgraph::utils::fmap(
|
||||
[](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
},
|
||||
histories);
|
||||
|
||||
memgraph::replication_coordination_glue::DatabaseHistory history{
|
||||
.db_uuid = db_uuid, .history = db_histories, .name = default_name};
|
||||
@@ -89,9 +91,11 @@ TEST_F(CoordinationUtils, MemgraphDbHistoryLastEpochDifferent) {
|
||||
memgraph::utils::UUID db_uuid;
|
||||
std::string default_name = std::string(memgraph::dbms::kDefaultDB);
|
||||
|
||||
auto db_histories = memgraph::utils::fmap(histories, [](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
});
|
||||
auto db_histories = memgraph::utils::fmap(
|
||||
[](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
},
|
||||
histories);
|
||||
|
||||
db_histories.back().second = 51;
|
||||
memgraph::replication_coordination_glue::DatabaseHistory history1{
|
||||
@@ -141,9 +145,11 @@ TEST_F(CoordinationUtils, MemgraphDbHistoryOneInstanceAheadFewEpochs) {
|
||||
memgraph::utils::UUID db_uuid;
|
||||
std::string default_name = std::string(memgraph::dbms::kDefaultDB);
|
||||
|
||||
auto db_histories = memgraph::utils::fmap(histories, [](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
});
|
||||
auto db_histories = memgraph::utils::fmap(
|
||||
[](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
},
|
||||
histories);
|
||||
|
||||
memgraph::replication_coordination_glue::DatabaseHistory history{
|
||||
.db_uuid = db_uuid, .history = db_histories, .name = default_name};
|
||||
@@ -156,10 +162,11 @@ TEST_F(CoordinationUtils, MemgraphDbHistoryOneInstanceAheadFewEpochs) {
|
||||
|
||||
histories.emplace_back(memgraph::utils::UUID{}, 60);
|
||||
histories.emplace_back(memgraph::utils::UUID{}, 65);
|
||||
auto db_histories_longest =
|
||||
memgraph::utils::fmap(histories, [](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
auto db_histories_longest = memgraph::utils::fmap(
|
||||
[](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
});
|
||||
},
|
||||
histories);
|
||||
|
||||
memgraph::replication_coordination_glue::DatabaseHistory history_longest{
|
||||
.db_uuid = db_uuid, .history = db_histories_longest, .name = default_name};
|
||||
@@ -193,9 +200,11 @@ TEST_F(CoordinationUtils, MemgraphDbHistoryInstancesHistoryDiverged) {
|
||||
memgraph::utils::UUID db_uuid;
|
||||
std::string default_name = std::string(memgraph::dbms::kDefaultDB);
|
||||
|
||||
auto db_histories = memgraph::utils::fmap(histories, [](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
});
|
||||
auto db_histories = memgraph::utils::fmap(
|
||||
[](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
},
|
||||
histories);
|
||||
|
||||
memgraph::replication_coordination_glue::DatabaseHistory history{
|
||||
.db_uuid = db_uuid, .history = db_histories, .name = default_name};
|
||||
@@ -208,10 +217,11 @@ TEST_F(CoordinationUtils, MemgraphDbHistoryInstancesHistoryDiverged) {
|
||||
auto oldest_commit_timestamp{5};
|
||||
auto newest_different_epoch = memgraph::utils::UUID{};
|
||||
histories.emplace_back(newest_different_epoch, oldest_commit_timestamp);
|
||||
auto db_histories_different =
|
||||
memgraph::utils::fmap(histories, [](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
auto db_histories_different = memgraph::utils::fmap(
|
||||
[](const std::pair<memgraph::utils::UUID, uint64_t> &pair) {
|
||||
return std::make_pair(std::string(pair.first), pair.second);
|
||||
});
|
||||
},
|
||||
histories);
|
||||
|
||||
memgraph::replication_coordination_glue::DatabaseHistory history_3{
|
||||
.db_uuid = db_uuid, .history = db_histories_different, .name = default_name};
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "nuraft/coordinator_cluster_state.hpp"
|
||||
#include "nuraft/coordinator_state_machine.hpp"
|
||||
#include "replication_coordination_glue/role.hpp"
|
||||
|
||||
#include "utils/file.hpp"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "json/json.hpp"
|
||||
|
||||
#include "libnuraft/nuraft.hxx"
|
||||
|
||||
using memgraph::coordination::CoordinatorClientConfig;
|
||||
using memgraph::coordination::CoordinatorClusterState;
|
||||
using memgraph::coordination::CoordinatorStateMachine;
|
||||
using memgraph::coordination::InstanceState;
|
||||
using memgraph::coordination::RaftLogAction;
|
||||
using memgraph::replication_coordination_glue::ReplicationMode;
|
||||
using memgraph::replication_coordination_glue::ReplicationRole;
|
||||
using nuraft::buffer;
|
||||
using nuraft::buffer_serializer;
|
||||
using nuraft::ptr;
|
||||
|
||||
class CoordinatorClusterStateTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {}
|
||||
|
||||
void TearDown() override {}
|
||||
|
||||
std::filesystem::path test_folder_{std::filesystem::temp_directory_path() /
|
||||
"MG_tests_unit_coordinator_cluster_state"};
|
||||
};
|
||||
|
||||
TEST_F(CoordinatorClusterStateTest, InstanceStateSerialization) {
|
||||
InstanceState instance_state{
|
||||
CoordinatorClientConfig{"instance3",
|
||||
"127.0.0.1",
|
||||
10112,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10001},
|
||||
.ssl = std::nullopt},
|
||||
ReplicationRole::MAIN};
|
||||
|
||||
nlohmann::json j = instance_state;
|
||||
InstanceState deserialized_instance_state = j.get<InstanceState>();
|
||||
|
||||
EXPECT_EQ(instance_state.config, deserialized_instance_state.config);
|
||||
EXPECT_EQ(instance_state.status, deserialized_instance_state.status);
|
||||
}
|
||||
|
||||
TEST_F(CoordinatorClusterStateTest, DoActionRegisterInstances) {
|
||||
auto coordinator_cluster_state = memgraph::coordination::CoordinatorClusterState{};
|
||||
|
||||
{
|
||||
CoordinatorClientConfig config{"instance1",
|
||||
"127.0.0.1",
|
||||
10111,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10001},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
auto buffer = CoordinatorStateMachine::SerializeRegisterInstance(config);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
|
||||
coordinator_cluster_state.DoAction(payload, action);
|
||||
}
|
||||
{
|
||||
CoordinatorClientConfig config{"instance2",
|
||||
"127.0.0.1",
|
||||
10112,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10002},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
auto buffer = CoordinatorStateMachine::SerializeRegisterInstance(config);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
|
||||
coordinator_cluster_state.DoAction(payload, action);
|
||||
}
|
||||
{
|
||||
CoordinatorClientConfig config{"instance3",
|
||||
"127.0.0.1",
|
||||
10113,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10003},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
auto buffer = CoordinatorStateMachine::SerializeRegisterInstance(config);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
|
||||
coordinator_cluster_state.DoAction(payload, action);
|
||||
}
|
||||
{
|
||||
CoordinatorClientConfig config{"instance4",
|
||||
"127.0.0.1",
|
||||
10114,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10004},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
auto buffer = CoordinatorStateMachine::SerializeRegisterInstance(config);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
|
||||
coordinator_cluster_state.DoAction(payload, action);
|
||||
}
|
||||
{
|
||||
CoordinatorClientConfig config{"instance5",
|
||||
"127.0.0.1",
|
||||
10115,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10005},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
auto buffer = CoordinatorStateMachine::SerializeRegisterInstance(config);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
|
||||
coordinator_cluster_state.DoAction(payload, action);
|
||||
}
|
||||
{
|
||||
CoordinatorClientConfig config{"instance6",
|
||||
"127.0.0.1",
|
||||
10116,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10006},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
auto buffer = CoordinatorStateMachine::SerializeRegisterInstance(config);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
|
||||
coordinator_cluster_state.DoAction(payload, action);
|
||||
}
|
||||
|
||||
ptr<buffer> data;
|
||||
coordinator_cluster_state.Serialize(data);
|
||||
|
||||
auto deserialized_coordinator_cluster_state = CoordinatorClusterState::Deserialize(*data);
|
||||
ASSERT_EQ(coordinator_cluster_state.GetInstances(), deserialized_coordinator_cluster_state.GetInstances());
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "nuraft/coordinator_state_machine.hpp"
|
||||
#include "nuraft/raft_log_action.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "json/json.hpp"
|
||||
|
||||
using memgraph::coordination::CoordinatorClientConfig;
|
||||
using memgraph::coordination::CoordinatorStateMachine;
|
||||
using memgraph::coordination::RaftLogAction;
|
||||
using memgraph::coordination::ReplClientInfo;
|
||||
using memgraph::replication_coordination_glue::ReplicationMode;
|
||||
using memgraph::utils::UUID;
|
||||
|
||||
class RaftLogSerialization : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {}
|
||||
|
||||
void TearDown() override {}
|
||||
|
||||
std::filesystem::path test_folder_{std::filesystem::temp_directory_path() / "MG_tests_unit_raft_log_serialization"};
|
||||
};
|
||||
|
||||
TEST_F(RaftLogSerialization, ReplClientInfo) {
|
||||
ReplClientInfo info{"instance_name", ReplicationMode::SYNC, "127.0.0.1", 10111};
|
||||
|
||||
nlohmann::json j = info;
|
||||
ReplClientInfo info2 = j.get<memgraph::coordination::ReplClientInfo>();
|
||||
|
||||
ASSERT_EQ(info, info2);
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, CoordinatorClientConfig) {
|
||||
CoordinatorClientConfig config{"instance3",
|
||||
"127.0.0.1",
|
||||
10112,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10001},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
nlohmann::json j = config;
|
||||
CoordinatorClientConfig config2 = j.get<memgraph::coordination::CoordinatorClientConfig>();
|
||||
|
||||
ASSERT_EQ(config, config2);
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, RaftLogActionRegister) {
|
||||
auto action = RaftLogAction::REGISTER_REPLICATION_INSTANCE;
|
||||
|
||||
nlohmann::json j = action;
|
||||
RaftLogAction action2 = j.get<memgraph::coordination::RaftLogAction>();
|
||||
|
||||
ASSERT_EQ(action, action2);
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, RaftLogActionUnregister) {
|
||||
auto action = RaftLogAction::UNREGISTER_REPLICATION_INSTANCE;
|
||||
|
||||
nlohmann::json j = action;
|
||||
RaftLogAction action2 = j.get<memgraph::coordination::RaftLogAction>();
|
||||
|
||||
ASSERT_EQ(action, action2);
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, RaftLogActionPromote) {
|
||||
auto action = RaftLogAction::SET_INSTANCE_AS_MAIN;
|
||||
|
||||
nlohmann::json j = action;
|
||||
RaftLogAction action2 = j.get<memgraph::coordination::RaftLogAction>();
|
||||
|
||||
ASSERT_EQ(action, action2);
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, RaftLogActionDemote) {
|
||||
auto action = RaftLogAction::SET_INSTANCE_AS_REPLICA;
|
||||
|
||||
nlohmann::json j = action;
|
||||
RaftLogAction action2 = j.get<memgraph::coordination::RaftLogAction>();
|
||||
|
||||
ASSERT_EQ(action, action2);
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, RaftLogActionUpdateUUID) {
|
||||
auto action = RaftLogAction::UPDATE_UUID;
|
||||
|
||||
nlohmann::json j = action;
|
||||
RaftLogAction action2 = j.get<memgraph::coordination::RaftLogAction>();
|
||||
|
||||
ASSERT_EQ(action, action2);
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, RegisterInstance) {
|
||||
CoordinatorClientConfig config{"instance3",
|
||||
"127.0.0.1",
|
||||
10112,
|
||||
std::chrono::seconds{1},
|
||||
std::chrono::seconds{5},
|
||||
std::chrono::seconds{10},
|
||||
{"instance_name", ReplicationMode::ASYNC, "replication_ip_address", 10001},
|
||||
.ssl = std::nullopt};
|
||||
|
||||
auto buffer = CoordinatorStateMachine::SerializeRegisterInstance(config);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
ASSERT_EQ(action, RaftLogAction::REGISTER_REPLICATION_INSTANCE);
|
||||
ASSERT_EQ(config, std::get<CoordinatorClientConfig>(payload));
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, UnregisterInstance) {
|
||||
auto buffer = CoordinatorStateMachine::SerializeUnregisterInstance("instance3");
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
ASSERT_EQ(action, RaftLogAction::UNREGISTER_REPLICATION_INSTANCE);
|
||||
ASSERT_EQ("instance3", std::get<std::string>(payload));
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, SetInstanceAsMain) {
|
||||
auto buffer = CoordinatorStateMachine::SerializeSetInstanceAsMain("instance3");
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
ASSERT_EQ(action, RaftLogAction::SET_INSTANCE_AS_MAIN);
|
||||
ASSERT_EQ("instance3", std::get<std::string>(payload));
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, SetInstanceAsReplica) {
|
||||
auto buffer = CoordinatorStateMachine::SerializeSetInstanceAsReplica("instance3");
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
ASSERT_EQ(action, RaftLogAction::SET_INSTANCE_AS_REPLICA);
|
||||
ASSERT_EQ("instance3", std::get<std::string>(payload));
|
||||
}
|
||||
|
||||
TEST_F(RaftLogSerialization, UpdateUUID) {
|
||||
UUID uuid;
|
||||
auto buffer = CoordinatorStateMachine::SerializeUpdateUUID(uuid);
|
||||
auto [payload, action] = CoordinatorStateMachine::DecodeLog(*buffer);
|
||||
ASSERT_EQ(action, RaftLogAction::UPDATE_UUID);
|
||||
ASSERT_EQ(uuid, std::get<UUID>(payload));
|
||||
}
|
||||
Reference in New Issue
Block a user