Compare commits
10 Commits
remove-gra
...
replicatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f9e044fcd | ||
|
|
efd3257479 | ||
|
|
189895370b | ||
|
|
4f873a6b4d | ||
|
|
ec6d35ff67 | ||
|
|
e125c5cd98 | ||
|
|
1ecf6ddab2 | ||
|
|
17ad671773 | ||
|
|
7386b786a9 | ||
|
|
6e758d3b5a |
68
.github/workflows/diff.yaml
vendored
68
.github/workflows/diff.yaml
vendored
@@ -268,6 +268,7 @@ jobs:
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
if: false
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose up -d
|
||||
@@ -275,6 +276,7 @@ jobs:
|
||||
docker-compose up -d
|
||||
|
||||
- name: Run e2e tests
|
||||
if: false
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
@@ -283,6 +285,7 @@ jobs:
|
||||
./run.sh
|
||||
|
||||
- name: Ensure Kafka and Pulsar are down
|
||||
if: false
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose down
|
||||
@@ -383,6 +386,71 @@ jobs:
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
|
||||
experimental_build_mt:
|
||||
name: "MultiTenancy replication build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build MT replication experimental binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -D MG_EXPERIMENTAL_REPLICATION_MULTITENANCY=ON ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Run e2e tests
|
||||
if: false
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
source ve3/bin/activate_e2e
|
||||
cd e2e
|
||||
|
||||
# Just the replication based e2e tests
|
||||
./run.sh "Replicate multitenancy"
|
||||
./run.sh "Show"
|
||||
./run.sh "Show while creating invalid state"
|
||||
./run.sh "Delete edge replication"
|
||||
./run.sh "Read-write benchmark"
|
||||
./run.sh "Index replication"
|
||||
./run.sh "Constraints"
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data(MultiTenancy replication build)"
|
||||
path: |
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
|
||||
release_jepsen_test:
|
||||
name: "Release Jepsen Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||
|
||||
10
.github/workflows/release_debian10.yaml
vendored
10
.github/workflows/release_debian10.yaml
vendored
@@ -1,7 +1,4 @@
|
||||
name: Release Debian 10
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -13,12 +10,7 @@ on:
|
||||
options:
|
||||
- Release
|
||||
- RelWithDebInfo
|
||||
push:
|
||||
branches:
|
||||
- "release/**"
|
||||
tags:
|
||||
- "v*.*.*-rc*"
|
||||
- "v*.*-rc*"
|
||||
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
|
||||
|
||||
10
.github/workflows/release_ubuntu2004.yaml
vendored
10
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -1,7 +1,4 @@
|
||||
name: Release Ubuntu 20.04
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -13,12 +10,7 @@ on:
|
||||
options:
|
||||
- Release
|
||||
- RelWithDebInfo
|
||||
push:
|
||||
branches:
|
||||
- "release/**"
|
||||
tags:
|
||||
- "v*.*.*-rc*"
|
||||
- "v*.*-rc*"
|
||||
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
|
||||
|
||||
8
.github/workflows/stress_test_large.yaml
vendored
8
.github/workflows/stress_test_large.yaml
vendored
@@ -1,7 +1,4 @@
|
||||
name: Stress test large
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -13,10 +10,7 @@ on:
|
||||
options:
|
||||
- Release
|
||||
- RelWithDebInfo
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*-rc*"
|
||||
- "v*.*-rc*"
|
||||
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
|
||||
|
||||
@@ -291,6 +291,16 @@ option(TSAN "Build with Thread Sanitizer. To get a reasonable performance option
|
||||
option(UBSAN "Build with Undefined Behaviour Sanitizer" OFF)
|
||||
|
||||
# Build feature flags
|
||||
option(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY "Feature flag for experimental replicaition of multitenacy" OFF)
|
||||
|
||||
if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||
set(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY OFF)
|
||||
message(FATAL_ERROR "MG_EXPERIMENTAL_REPLICATION_MULTITENANCY with community edition build isn't possible")
|
||||
endif ()
|
||||
|
||||
if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||
add_compile_definitions(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||
endif ()
|
||||
|
||||
if (TEST_COVERAGE)
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
|
||||
@@ -10,11 +10,12 @@ target_sources(mg-coordination
|
||||
include/coordination/coordinator_exceptions.hpp
|
||||
include/coordination/coordinator_slk.hpp
|
||||
include/coordination/coordinator_instance.hpp
|
||||
include/coordination/coordinator_cluster_config.hpp
|
||||
include/coordination/coordinator_handlers.hpp
|
||||
include/coordination/constants.hpp
|
||||
include/coordination/instance_status.hpp
|
||||
include/coordination/replication_instance.hpp
|
||||
include/coordination/raft_state.hpp
|
||||
include/coordination/raft_instance.hpp
|
||||
|
||||
include/nuraft/coordinator_log_store.hpp
|
||||
include/nuraft/coordinator_state_machine.hpp
|
||||
@@ -28,7 +29,7 @@ target_sources(mg-coordination
|
||||
coordinator_handlers.cpp
|
||||
coordinator_instance.cpp
|
||||
replication_instance.cpp
|
||||
raft_state.cpp
|
||||
raft_instance.cpp
|
||||
|
||||
coordinator_log_store.cpp
|
||||
coordinator_state_machine.cpp
|
||||
|
||||
@@ -41,21 +41,16 @@ CoordinatorClient::CoordinatorClient(CoordinatorInstance *coord_instance, Coordi
|
||||
auto CoordinatorClient::InstanceName() const -> std::string { return config_.instance_name; }
|
||||
auto CoordinatorClient::SocketAddress() const -> std::string { return rpc_client_.Endpoint().SocketAddress(); }
|
||||
|
||||
auto CoordinatorClient::InstanceDownTimeoutSec() const -> std::chrono::seconds {
|
||||
return config_.instance_down_timeout_sec;
|
||||
}
|
||||
|
||||
void CoordinatorClient::StartFrequentCheck() {
|
||||
if (instance_checker_.IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MG_ASSERT(config_.instance_health_check_frequency_sec > std::chrono::seconds(0),
|
||||
MG_ASSERT(config_.health_check_frequency_sec > std::chrono::seconds(0),
|
||||
"Health check frequency must be greater than 0");
|
||||
|
||||
instance_checker_.Run(
|
||||
config_.instance_name, config_.instance_health_check_frequency_sec,
|
||||
[this, instance_name = config_.instance_name] {
|
||||
config_.instance_name, config_.health_check_frequency_sec, [this, instance_name = config_.instance_name] {
|
||||
try {
|
||||
spdlog::trace("Sending frequent heartbeat to machine {} on {}", instance_name,
|
||||
rpc_client_.Endpoint().SocketAddress());
|
||||
@@ -126,33 +121,5 @@ auto CoordinatorClient::SendSwapMainUUIDRpc(const utils::UUID &uuid) const -> bo
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CoordinatorClient::SendUnregisterReplicaRpc(std::string const &instance_name) const -> bool {
|
||||
try {
|
||||
auto stream{rpc_client_.Stream<UnregisterReplicaRpc>(instance_name)};
|
||||
if (!stream.AwaitResponse().success) {
|
||||
spdlog::error("Failed to receive successful RPC response for unregistering replica!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (rpc::RpcFailedException const &) {
|
||||
spdlog::error("Failed to unregister replica!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CoordinatorClient::SendEnableWritingOnMainRpc() const -> bool {
|
||||
try {
|
||||
auto stream{rpc_client_.Stream<EnableWritingOnMainRpc>()};
|
||||
if (!stream.AwaitResponse().success) {
|
||||
spdlog::error("Failed to receive successful RPC response for enabling writing on main!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (rpc::RpcFailedException const &) {
|
||||
spdlog::error("Failed to enable writing on main!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
|
||||
@@ -39,18 +39,6 @@ void CoordinatorHandlers::Register(memgraph::coordination::CoordinatorServer &se
|
||||
spdlog::info("Received SwapMainUUIDRPC on coordinator server");
|
||||
CoordinatorHandlers::SwapMainUUIDHandler(replication_handler, req_reader, res_builder);
|
||||
});
|
||||
|
||||
server.Register<coordination::UnregisterReplicaRpc>(
|
||||
[&replication_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received UnregisterReplicaRpc on coordinator server");
|
||||
CoordinatorHandlers::UnregisterReplicaHandler(replication_handler, req_reader, res_builder);
|
||||
});
|
||||
|
||||
server.Register<coordination::EnableWritingOnMainRpc>(
|
||||
[&replication_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received EnableWritingOnMainRpc on coordinator server");
|
||||
CoordinatorHandlers::EnableWritingOnMainHandler(replication_handler, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::SwapMainUUIDHandler(replication::ReplicationHandler &replication_handler,
|
||||
@@ -154,58 +142,9 @@ void CoordinatorHandlers::PromoteReplicaToMainHandler(replication::ReplicationHa
|
||||
}
|
||||
}
|
||||
}
|
||||
spdlog::info("Promote replica to main was success {}", std::string(req.main_uuid_));
|
||||
spdlog::error(fmt::format("FICO : Promote replica to main was success {}", std::string(req.main_uuid_)));
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::UnregisterReplicaHandler(replication::ReplicationHandler &replication_handler,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
if (!replication_handler.IsMain()) {
|
||||
spdlog::error("Unregistering replica must be performed on main.");
|
||||
slk::Save(coordination::UnregisterReplicaRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
coordination::UnregisterReplicaReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
auto res = replication_handler.UnregisterReplica(req.instance_name);
|
||||
switch (res) {
|
||||
using enum memgraph::query::UnregisterReplicaResult;
|
||||
case SUCCESS:
|
||||
slk::Save(coordination::UnregisterReplicaRes{true}, res_builder);
|
||||
break;
|
||||
case NOT_MAIN:
|
||||
spdlog::error("Unregistering replica must be performed on main.");
|
||||
slk::Save(coordination::UnregisterReplicaRes{false}, res_builder);
|
||||
break;
|
||||
case CAN_NOT_UNREGISTER:
|
||||
spdlog::error("Could not unregister replica.");
|
||||
slk::Save(coordination::UnregisterReplicaRes{false}, res_builder);
|
||||
break;
|
||||
case COULD_NOT_BE_PERSISTED:
|
||||
spdlog::error("Could not persist replica unregistration.");
|
||||
slk::Save(coordination::UnregisterReplicaRes{false}, res_builder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::EnableWritingOnMainHandler(replication::ReplicationHandler &replication_handler,
|
||||
slk::Reader * /*req_reader*/, slk::Builder *res_builder) {
|
||||
if (!replication_handler.IsMain()) {
|
||||
spdlog::error("Enable writing on main must be performed on main!");
|
||||
slk::Save(coordination::EnableWritingOnMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!replication_handler.GetReplState().EnableWritingOnMain()) {
|
||||
spdlog::error("Enabling writing on main failed!");
|
||||
slk::Save(coordination::EnableWritingOnMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
slk::Save(coordination::EnableWritingOnMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "nuraft/coordinator_state_machine.hpp"
|
||||
#include "nuraft/coordinator_state_manager.hpp"
|
||||
#include "utils/counter.hpp"
|
||||
#include "utils/functional.hpp"
|
||||
|
||||
#include <range/v3/view.hpp>
|
||||
#include <shared_mutex>
|
||||
@@ -28,108 +27,127 @@ using nuraft::ptr;
|
||||
using nuraft::srv_config;
|
||||
|
||||
CoordinatorInstance::CoordinatorInstance()
|
||||
: raft_state_(RaftState::MakeRaftState(
|
||||
[this] { std::ranges::for_each(repl_instances_, &ReplicationInstance::StartFrequentCheck); },
|
||||
[this] { std::ranges::for_each(repl_instances_, &ReplicationInstance::StopFrequentCheck); })) {
|
||||
auto find_repl_instance = [](CoordinatorInstance *self,
|
||||
std::string_view repl_instance_name) -> ReplicationInstance & {
|
||||
auto repl_instance =
|
||||
std::ranges::find_if(self->repl_instances_, [repl_instance_name](ReplicationInstance const &instance) {
|
||||
return instance.InstanceName() == repl_instance_name;
|
||||
});
|
||||
: self_([this] { std::ranges::for_each(repl_instances_, &ReplicationInstance::StartFrequentCheck); },
|
||||
[this] { std::ranges::for_each(repl_instances_, &ReplicationInstance::StopFrequentCheck); }) {
|
||||
auto find_instance = [](CoordinatorInstance *coord_instance,
|
||||
std::string_view instance_name) -> ReplicationInstance & {
|
||||
auto instance = std::ranges::find_if(
|
||||
coord_instance->repl_instances_,
|
||||
[instance_name](ReplicationInstance const &instance) { return instance.InstanceName() == instance_name; });
|
||||
|
||||
MG_ASSERT(repl_instance != self->repl_instances_.end(), "Instance {} not found during callback!",
|
||||
repl_instance_name);
|
||||
return *repl_instance;
|
||||
MG_ASSERT(instance != coord_instance->repl_instances_.end(), "Instance {} not found during callback!",
|
||||
instance_name);
|
||||
return *instance;
|
||||
};
|
||||
|
||||
replica_succ_cb_ = [find_repl_instance](CoordinatorInstance *self, std::string_view repl_instance_name) -> void {
|
||||
auto lock = std::lock_guard{self->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing replica successful callback", repl_instance_name);
|
||||
auto &repl_instance = find_repl_instance(self, repl_instance_name);
|
||||
replica_succ_cb_ = [find_instance](CoordinatorInstance *coord_instance, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_instance->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing replica successful callback", instance_name);
|
||||
find_instance(coord_instance, instance_name).OnSuccessPing();
|
||||
};
|
||||
|
||||
if (!repl_instance.EnsureReplicaHasCorrectMainUUID(self->GetMainUUID())) {
|
||||
spdlog::error(
|
||||
fmt::format("Failed to swap uuid for replica instance {} which is alive", repl_instance.InstanceName()));
|
||||
replica_fail_cb_ = [find_instance](CoordinatorInstance *coord_instance, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_instance->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing replica failure callback", instance_name);
|
||||
find_instance(coord_instance, instance_name).OnFailPing();
|
||||
};
|
||||
|
||||
main_succ_cb_ = [find_instance](CoordinatorInstance *coord_instance, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_instance->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing main successful callback", instance_name);
|
||||
|
||||
auto &instance = find_instance(coord_instance, instance_name);
|
||||
|
||||
if (instance.IsAlive()) {
|
||||
instance.OnSuccessPing();
|
||||
return;
|
||||
}
|
||||
|
||||
repl_instance.OnSuccessPing();
|
||||
};
|
||||
|
||||
replica_fail_cb_ = [find_repl_instance](CoordinatorInstance *self, std::string_view repl_instance_name) -> void {
|
||||
auto lock = std::lock_guard{self->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing replica failure callback", repl_instance_name);
|
||||
auto &repl_instance = find_repl_instance(self, repl_instance_name);
|
||||
repl_instance.OnFailPing();
|
||||
// We need to restart main uuid from instance since it was "down" at least a second
|
||||
// There is slight delay, if we choose to use isAlive, instance can be down and back up in less than
|
||||
// our isAlive time difference, which would lead to instance setting UUID to nullopt and stopping accepting any
|
||||
// incoming RPCs from valid main
|
||||
// TODO(antoniofilipovic) this needs here more complex logic
|
||||
// We need to get id of main replica is listening to on successful ping
|
||||
// and swap it to correct uuid if it failed
|
||||
repl_instance.ResetMainUUID();
|
||||
};
|
||||
|
||||
main_succ_cb_ = [find_repl_instance](CoordinatorInstance *self, std::string_view repl_instance_name) -> void {
|
||||
auto lock = std::lock_guard{self->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing main successful callback", repl_instance_name);
|
||||
|
||||
auto &repl_instance = find_repl_instance(self, repl_instance_name);
|
||||
|
||||
if (repl_instance.IsAlive()) {
|
||||
repl_instance.OnSuccessPing();
|
||||
bool const is_latest_main = !coord_instance->ClusterHasAliveMain_();
|
||||
if (is_latest_main) {
|
||||
spdlog::info("Instance {} is the latest main", instance_name);
|
||||
instance.OnSuccessPing();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &repl_instance_uuid = repl_instance.GetMainUUID();
|
||||
MG_ASSERT(repl_instance_uuid.has_value(), "Instance must have uuid set.");
|
||||
|
||||
auto const curr_main_uuid = self->GetMainUUID();
|
||||
if (curr_main_uuid == repl_instance_uuid.value()) {
|
||||
if (!repl_instance.EnableWritingOnMain()) {
|
||||
spdlog::error("Failed to enable writing on main instance {}", repl_instance_name);
|
||||
return;
|
||||
}
|
||||
|
||||
repl_instance.OnSuccessPing();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(antoniof) make demoteToReplica idempotent since main can be demoted to replica but
|
||||
// swapUUID can fail
|
||||
if (repl_instance.DemoteToReplica(self->replica_succ_cb_, self->replica_fail_cb_)) {
|
||||
repl_instance.OnSuccessPing();
|
||||
spdlog::info("Instance {} demoted to replica", repl_instance_name);
|
||||
bool const demoted = instance.DemoteToReplica(coord_instance->replica_succ_cb_, coord_instance->replica_fail_cb_);
|
||||
if (demoted) {
|
||||
instance.OnSuccessPing();
|
||||
spdlog::info("Instance {} demoted to replica", instance_name);
|
||||
} else {
|
||||
spdlog::error("Instance {} failed to become replica", repl_instance_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repl_instance.SendSwapAndUpdateUUID(curr_main_uuid)) {
|
||||
spdlog::error(fmt::format("Failed to swap uuid for demoted main instance {}", repl_instance.InstanceName()));
|
||||
return;
|
||||
spdlog::error("Instance {} failed to become replica", instance_name);
|
||||
}
|
||||
};
|
||||
|
||||
main_fail_cb_ = [find_repl_instance](CoordinatorInstance *self, std::string_view repl_instance_name) -> void {
|
||||
auto lock = std::lock_guard{self->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing main failure callback", repl_instance_name);
|
||||
auto &repl_instance = find_repl_instance(self, repl_instance_name);
|
||||
repl_instance.OnFailPing();
|
||||
const auto &repl_instance_uuid = repl_instance.GetMainUUID();
|
||||
MG_ASSERT(repl_instance_uuid.has_value(), "Instance must have uuid set");
|
||||
main_fail_cb_ = [find_instance](CoordinatorInstance *coord_instance, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_instance->coord_instance_lock_};
|
||||
spdlog::trace("Instance {} performing main failure callback", instance_name);
|
||||
find_instance(coord_instance, instance_name).OnFailPing();
|
||||
|
||||
if (!repl_instance.IsAlive() && self->GetMainUUID() == repl_instance_uuid.value()) {
|
||||
if (!coord_instance->ClusterHasAliveMain_()) {
|
||||
spdlog::info("Cluster without main instance, trying automatic failover");
|
||||
self->TryFailover(); // TODO: (andi) Initiate failover
|
||||
coord_instance->TryFailover();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::ClusterHasAliveMain_() const -> bool {
|
||||
auto const alive_main = [](ReplicationInstance const &instance) { return instance.IsMain() && instance.IsAlive(); };
|
||||
return std::ranges::any_of(repl_instances_, alive_main);
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::TryFailover() -> void {
|
||||
auto alive_replicas = repl_instances_ | ranges::views::filter(&ReplicationInstance::IsReplica) |
|
||||
ranges::views::filter(&ReplicationInstance::IsAlive);
|
||||
|
||||
if (ranges::empty(alive_replicas)) {
|
||||
spdlog::warn("Failover failed since all replicas are down!");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Smarter choice
|
||||
auto chosen_replica_instance = ranges::begin(alive_replicas);
|
||||
|
||||
chosen_replica_instance->PauseFrequentCheck();
|
||||
utils::OnScopeExit scope_exit{[&chosen_replica_instance] { chosen_replica_instance->ResumeFrequentCheck(); }};
|
||||
|
||||
auto const potential_new_main_uuid = utils::UUID{};
|
||||
|
||||
auto const is_not_chosen_replica_instance = [&chosen_replica_instance](ReplicationInstance &instance) {
|
||||
return instance != *chosen_replica_instance;
|
||||
};
|
||||
|
||||
// If for some replicas swap fails, for others on successful ping we will revert back on next change
|
||||
// or we will do failover first again and then it will be consistent again
|
||||
for (auto &other_replica_instance : alive_replicas | ranges::views::filter(is_not_chosen_replica_instance)) {
|
||||
if (!other_replica_instance.SendSwapAndUpdateUUID(potential_new_main_uuid)) {
|
||||
spdlog::error(fmt::format("Failed to swap uuid for instance {} which is alive, aborting failover",
|
||||
other_replica_instance.InstanceName()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ReplClientInfo> repl_clients_info;
|
||||
repl_clients_info.reserve(repl_instances_.size() - 1);
|
||||
std::ranges::transform(repl_instances_ | ranges::views::filter(is_not_chosen_replica_instance),
|
||||
std::back_inserter(repl_clients_info), &ReplicationInstance::ReplicationClientInfo);
|
||||
|
||||
if (!chosen_replica_instance->PromoteToMain(potential_new_main_uuid, std::move(repl_clients_info), main_succ_cb_,
|
||||
main_fail_cb_)) {
|
||||
spdlog::warn("Failover failed since promoting replica to main failed!");
|
||||
return;
|
||||
}
|
||||
chosen_replica_instance->SetNewMainUUID(potential_new_main_uuid);
|
||||
main_uuid_ = potential_new_main_uuid;
|
||||
|
||||
spdlog::info("Failover successful! Instance {} promoted to main.", chosen_replica_instance->InstanceName());
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::ShowInstances() const -> std::vector<InstanceStatus> {
|
||||
auto const coord_instances = raft_state_.GetAllCoordinators();
|
||||
auto const coord_instances = self_.GetAllCoordinators();
|
||||
|
||||
std::vector<InstanceStatus> instances_status;
|
||||
instances_status.reserve(repl_instances_.size() + coord_instances.size());
|
||||
|
||||
auto const stringify_repl_role = [](ReplicationInstance const &instance) -> std::string {
|
||||
if (!instance.IsAlive()) return "unknown";
|
||||
@@ -152,7 +170,8 @@ auto CoordinatorInstance::ShowInstances() const -> std::vector<InstanceStatus> {
|
||||
// CoordinatorState to every instance, we can be smarter about this using our RPC.
|
||||
};
|
||||
|
||||
auto instances_status = utils::fmap(coord_instance_to_status, coord_instances);
|
||||
std::ranges::transform(coord_instances, std::back_inserter(instances_status), coord_instance_to_status);
|
||||
|
||||
{
|
||||
auto lock = std::shared_lock{coord_instance_lock_};
|
||||
std::ranges::transform(repl_instances_, std::back_inserter(instances_status), repl_instance_to_status);
|
||||
@@ -161,51 +180,6 @@ auto CoordinatorInstance::ShowInstances() const -> std::vector<InstanceStatus> {
|
||||
return instances_status;
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::TryFailover() -> void {
|
||||
auto alive_replicas = repl_instances_ | ranges::views::filter(&ReplicationInstance::IsReplica) |
|
||||
ranges::views::filter(&ReplicationInstance::IsAlive);
|
||||
|
||||
if (ranges::empty(alive_replicas)) {
|
||||
spdlog::warn("Failover failed since all replicas are down!");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Smarter choice
|
||||
auto new_main = ranges::begin(alive_replicas);
|
||||
|
||||
new_main->PauseFrequentCheck();
|
||||
utils::OnScopeExit scope_exit{[&new_main] { new_main->ResumeFrequentCheck(); }};
|
||||
|
||||
auto const is_not_new_main = [&new_main](ReplicationInstance &instance) {
|
||||
return instance.InstanceName() != new_main->InstanceName();
|
||||
};
|
||||
|
||||
auto const new_main_uuid = utils::UUID{};
|
||||
// If for some replicas swap fails, for others on successful ping we will revert back on next change
|
||||
// or we will do failover first again and then it will be consistent again
|
||||
for (auto &other_replica_instance : alive_replicas | ranges::views::filter(is_not_new_main)) {
|
||||
if (!other_replica_instance.SendSwapAndUpdateUUID(new_main_uuid)) {
|
||||
spdlog::error(fmt::format("Failed to swap uuid for instance {} which is alive, aborting failover",
|
||||
other_replica_instance.InstanceName()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: (andi) fmap compliant
|
||||
ReplicationClientsInfo repl_clients_info;
|
||||
repl_clients_info.reserve(repl_instances_.size() - 1);
|
||||
std::ranges::transform(repl_instances_ | ranges::views::filter(is_not_new_main),
|
||||
std::back_inserter(repl_clients_info), &ReplicationInstance::ReplicationClientInfo);
|
||||
|
||||
if (!new_main->PromoteToMain(new_main_uuid, std::move(repl_clients_info), main_succ_cb_, main_fail_cb_)) {
|
||||
spdlog::warn("Failover failed since promoting replica to main failed!");
|
||||
return;
|
||||
}
|
||||
// TODO: (andi) This should be replicated across all coordinator instances with Raft log
|
||||
SetMainUUID(new_main_uuid);
|
||||
spdlog::info("Failover successful! Instance {} promoted to main.", new_main->InstanceName());
|
||||
}
|
||||
|
||||
// TODO: (andi) Make sure you cannot put coordinator instance to the main
|
||||
auto CoordinatorInstance::SetReplicationInstanceToMain(std::string instance_name)
|
||||
-> SetInstanceToMainCoordinatorStatus {
|
||||
@@ -225,31 +199,34 @@ auto CoordinatorInstance::SetReplicationInstanceToMain(std::string instance_name
|
||||
new_main->PauseFrequentCheck();
|
||||
utils::OnScopeExit scope_exit{[&new_main] { new_main->ResumeFrequentCheck(); }};
|
||||
|
||||
ReplicationClientsInfo repl_clients_info;
|
||||
repl_clients_info.reserve(repl_instances_.size() - 1);
|
||||
|
||||
auto const is_not_new_main = [&instance_name](ReplicationInstance const &instance) {
|
||||
return instance.InstanceName() != instance_name;
|
||||
};
|
||||
|
||||
auto const new_main_uuid = utils::UUID{};
|
||||
auto potential_new_main_uuid = utils::UUID{};
|
||||
spdlog::trace("Generated potential new main uuid");
|
||||
|
||||
for (auto &other_instance : repl_instances_ | ranges::views::filter(is_not_new_main)) {
|
||||
if (!other_instance.SendSwapAndUpdateUUID(new_main_uuid)) {
|
||||
if (!other_instance.SendSwapAndUpdateUUID(potential_new_main_uuid)) {
|
||||
spdlog::error(
|
||||
fmt::format("Failed to swap uuid for instance {}, aborting failover", other_instance.InstanceName()));
|
||||
return SetInstanceToMainCoordinatorStatus::SWAP_UUID_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
ReplicationClientsInfo repl_clients_info;
|
||||
repl_clients_info.reserve(repl_instances_.size() - 1);
|
||||
std::ranges::transform(repl_instances_ | ranges::views::filter(is_not_new_main),
|
||||
std::back_inserter(repl_clients_info), &ReplicationInstance::ReplicationClientInfo);
|
||||
std::back_inserter(repl_clients_info),
|
||||
[](const ReplicationInstance &instance) { return instance.ReplicationClientInfo(); });
|
||||
|
||||
if (!new_main->PromoteToMain(new_main_uuid, std::move(repl_clients_info), main_succ_cb_, main_fail_cb_)) {
|
||||
if (!new_main->PromoteToMain(potential_new_main_uuid, std::move(repl_clients_info), main_succ_cb_, main_fail_cb_)) {
|
||||
return SetInstanceToMainCoordinatorStatus::COULD_NOT_PROMOTE_TO_MAIN;
|
||||
}
|
||||
|
||||
// TODO: (andi) This should be replicated across all coordinator instances with Raft log
|
||||
SetMainUUID(new_main_uuid);
|
||||
new_main->SetNewMainUUID(potential_new_main_uuid);
|
||||
main_uuid_ = potential_new_main_uuid;
|
||||
spdlog::info("Instance {} promoted to main", instance_name);
|
||||
return SetInstanceToMainCoordinatorStatus::SUCCESS;
|
||||
}
|
||||
@@ -258,10 +235,8 @@ auto CoordinatorInstance::RegisterReplicationInstance(CoordinatorClientConfig co
|
||||
-> RegisterInstanceCoordinatorStatus {
|
||||
auto lock = std::lock_guard{coord_instance_lock_};
|
||||
|
||||
auto instance_name = config.instance_name;
|
||||
|
||||
auto const name_matches = [&instance_name](ReplicationInstance const &instance) {
|
||||
return instance.InstanceName() == instance_name;
|
||||
auto const name_matches = [&config](ReplicationInstance const &instance) {
|
||||
return instance.InstanceName() == config.instance_name;
|
||||
};
|
||||
|
||||
if (std::ranges::any_of(repl_instances_, name_matches)) {
|
||||
@@ -276,21 +251,20 @@ auto CoordinatorInstance::RegisterReplicationInstance(CoordinatorClientConfig co
|
||||
return RegisterInstanceCoordinatorStatus::ENDPOINT_EXISTS;
|
||||
}
|
||||
|
||||
if (!raft_state_.RequestLeadership()) {
|
||||
if (!self_.RequestLeadership()) {
|
||||
return RegisterInstanceCoordinatorStatus::NOT_LEADER;
|
||||
}
|
||||
|
||||
auto const res = raft_state_.AppendRegisterReplicationInstance(instance_name);
|
||||
auto const res = self_.AppendRegisterReplicationInstance(config.instance_name);
|
||||
if (!res->get_accepted()) {
|
||||
spdlog::error(
|
||||
"Failed to accept request for registering instance {}. Most likely the reason is that the instance is not "
|
||||
"the "
|
||||
"Failed to accept request for registering instance {}. Most likely the reason is that the instance is not the "
|
||||
"leader.",
|
||||
config.instance_name);
|
||||
return RegisterInstanceCoordinatorStatus::RAFT_COULD_NOT_ACCEPT;
|
||||
}
|
||||
|
||||
spdlog::info("Request for registering instance {} accepted", instance_name);
|
||||
spdlog::info("Request for registering instance {} accepted", config.instance_name);
|
||||
try {
|
||||
repl_instances_.emplace_back(this, std::move(config), replica_succ_cb_, replica_fail_cb_);
|
||||
} catch (CoordinatorRegisterInstanceException const &) {
|
||||
@@ -298,52 +272,18 @@ auto CoordinatorInstance::RegisterReplicationInstance(CoordinatorClientConfig co
|
||||
}
|
||||
|
||||
if (res->get_result_code() != nuraft::cmd_result_code::OK) {
|
||||
spdlog::error("Failed to register instance {} with error code {}", instance_name, res->get_result_code());
|
||||
spdlog::error("Failed to register instance {} with error code {}", config.instance_name, res->get_result_code());
|
||||
return RegisterInstanceCoordinatorStatus::RAFT_COULD_NOT_APPEND;
|
||||
}
|
||||
|
||||
spdlog::info("Instance {} registered", instance_name);
|
||||
spdlog::info("Instance {} registered", config.instance_name);
|
||||
return RegisterInstanceCoordinatorStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::UnregisterReplicationInstance(std::string instance_name)
|
||||
-> UnregisterInstanceCoordinatorStatus {
|
||||
auto lock = std::lock_guard{coord_instance_lock_};
|
||||
|
||||
auto const name_matches = [&instance_name](ReplicationInstance const &instance) {
|
||||
return instance.InstanceName() == instance_name;
|
||||
};
|
||||
|
||||
auto inst_to_remove = std::ranges::find_if(repl_instances_, name_matches);
|
||||
if (inst_to_remove == repl_instances_.end()) {
|
||||
return UnregisterInstanceCoordinatorStatus::NO_INSTANCE_WITH_NAME;
|
||||
}
|
||||
|
||||
if (inst_to_remove->IsMain() && inst_to_remove->IsAlive()) {
|
||||
return UnregisterInstanceCoordinatorStatus::IS_MAIN;
|
||||
}
|
||||
|
||||
inst_to_remove->StopFrequentCheck();
|
||||
auto curr_main = std::ranges::find_if(repl_instances_, &ReplicationInstance::IsMain);
|
||||
MG_ASSERT(curr_main != repl_instances_.end(), "There must be a main instance when unregistering a replica");
|
||||
if (!curr_main->SendUnregisterReplicaRpc(instance_name)) {
|
||||
inst_to_remove->StartFrequentCheck();
|
||||
return UnregisterInstanceCoordinatorStatus::RPC_FAILED;
|
||||
}
|
||||
std::erase_if(repl_instances_, name_matches);
|
||||
|
||||
return UnregisterInstanceCoordinatorStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::AddCoordinatorInstance(uint32_t raft_server_id, uint32_t raft_port, std::string raft_address)
|
||||
-> void {
|
||||
raft_state_.AddCoordinatorInstance(raft_server_id, raft_port, std::move(raft_address));
|
||||
self_.AddCoordinatorInstance(raft_server_id, raft_port, std::move(raft_address));
|
||||
}
|
||||
|
||||
auto CoordinatorInstance::GetMainUUID() const -> utils::UUID { return main_uuid_; }
|
||||
|
||||
// TODO: (andi) Add to the RAFT log.
|
||||
auto CoordinatorInstance::SetMainUUID(utils::UUID new_uuid) -> void { main_uuid_ = new_uuid; }
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "nuraft/coordinator_log_store.hpp"
|
||||
|
||||
#include "coordination/coordinator_exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
@@ -133,7 +132,7 @@ ptr<buffer> CoordinatorLogStore::pack(uint64_t index, int32 cnt) {
|
||||
auto lock = std::lock_guard{logs_lock_};
|
||||
le = logs_[i];
|
||||
}
|
||||
MG_ASSERT(le.get(), "Could not find log entry at index {}", i);
|
||||
assert(le.get());
|
||||
auto buf = le->serialize();
|
||||
size_total += buf->size();
|
||||
logs.push_back(buf);
|
||||
@@ -144,8 +143,9 @@ ptr<buffer> CoordinatorLogStore::pack(uint64_t index, int32 cnt) {
|
||||
buf_out->put((int32)cnt);
|
||||
|
||||
for (auto &entry : logs) {
|
||||
buf_out->put(static_cast<int32>(entry->size()));
|
||||
buf_out->put(*entry);
|
||||
auto &bb = entry; // TODO: (andi) This smells like not needed
|
||||
buf_out->put(static_cast<int32>(bb->size()));
|
||||
buf_out->put(*bb);
|
||||
}
|
||||
return buf_out;
|
||||
}
|
||||
|
||||
@@ -52,34 +52,6 @@ void DemoteMainToReplicaRes::Load(DemoteMainToReplicaRes *self, memgraph::slk::R
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void UnregisterReplicaReq::Save(UnregisterReplicaReq const &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void UnregisterReplicaReq::Load(UnregisterReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void UnregisterReplicaRes::Save(UnregisterReplicaRes const &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void UnregisterReplicaRes::Load(UnregisterReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void EnableWritingOnMainRes::Save(EnableWritingOnMainRes const &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void EnableWritingOnMainRes::Load(EnableWritingOnMainRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void EnableWritingOnMainReq::Save(EnableWritingOnMainReq const &self, memgraph::slk::Builder *builder) {}
|
||||
|
||||
void EnableWritingOnMainReq::Load(EnableWritingOnMainReq *self, memgraph::slk::Reader *reader) {}
|
||||
|
||||
} // namespace coordination
|
||||
|
||||
constexpr utils::TypeInfo coordination::PromoteReplicaToMainReq::kType{utils::TypeId::COORD_FAILOVER_REQ,
|
||||
@@ -92,21 +64,8 @@ constexpr utils::TypeInfo coordination::DemoteMainToReplicaReq::kType{utils::Typ
|
||||
"CoordDemoteToReplicaReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::DemoteMainToReplicaRes::kType{utils::TypeId::COORD_SET_REPL_MAIN_RES,
|
||||
|
||||
"CoordDemoteToReplicaRes", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::UnregisterReplicaReq::kType{utils::TypeId::COORD_UNREGISTER_REPLICA_REQ,
|
||||
"UnregisterReplicaReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::UnregisterReplicaRes::kType{utils::TypeId::COORD_UNREGISTER_REPLICA_RES,
|
||||
"UnregisterReplicaRes", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::EnableWritingOnMainReq::kType{utils::TypeId::COORD_ENABLE_WRITING_ON_MAIN_REQ,
|
||||
"CoordEnableWritingOnMainReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::EnableWritingOnMainRes::kType{utils::TypeId::COORD_ENABLE_WRITING_ON_MAIN_RES,
|
||||
"CoordEnableWritingOnMainRes", nullptr};
|
||||
|
||||
namespace slk {
|
||||
|
||||
void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) {
|
||||
@@ -143,30 +102,6 @@ void Load(memgraph::coordination::DemoteMainToReplicaRes *self, memgraph::slk::R
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
void Save(memgraph::coordination::UnregisterReplicaReq const &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.instance_name, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::UnregisterReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->instance_name, reader);
|
||||
}
|
||||
|
||||
void Save(memgraph::coordination::UnregisterReplicaRes const &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::UnregisterReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
void Save(memgraph::coordination::EnableWritingOnMainRes const &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::EnableWritingOnMainRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
} // namespace slk
|
||||
|
||||
} // namespace memgraph
|
||||
|
||||
@@ -56,20 +56,6 @@ auto CoordinatorState::RegisterReplicationInstance(CoordinatorClientConfig confi
|
||||
data_);
|
||||
}
|
||||
|
||||
auto CoordinatorState::UnregisterReplicationInstance(std::string instance_name) -> UnregisterInstanceCoordinatorStatus {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorInstance>(data_),
|
||||
"Coordinator cannot unregister instance since variant holds wrong alternative");
|
||||
|
||||
return std::visit(
|
||||
memgraph::utils::Overloaded{[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||
return UnregisterInstanceCoordinatorStatus::NOT_COORDINATOR;
|
||||
},
|
||||
[&instance_name](CoordinatorInstance &coordinator_instance) {
|
||||
return coordinator_instance.UnregisterReplicationInstance(instance_name);
|
||||
}},
|
||||
data_);
|
||||
}
|
||||
|
||||
auto CoordinatorState::SetReplicationInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorInstance>(data_),
|
||||
"Coordinator cannot register replica since variant holds wrong alternative");
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "utils/uuid.hpp"
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "rpc/client.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
@@ -46,24 +46,17 @@ class CoordinatorClient {
|
||||
auto SocketAddress() const -> std::string;
|
||||
|
||||
[[nodiscard]] auto DemoteToReplica() const -> bool;
|
||||
// TODO: (andi) Consistent naming
|
||||
auto SendPromoteReplicaToMainRpc(const utils::UUID &uuid, ReplicationClientsInfo replication_clients_info) const
|
||||
-> bool;
|
||||
|
||||
auto SendSwapMainUUIDRpc(const utils::UUID &uuid) const -> bool;
|
||||
|
||||
auto SendUnregisterReplicaRpc(std::string const &instance_name) const -> bool;
|
||||
|
||||
auto SendEnableWritingOnMainRpc() const -> bool;
|
||||
|
||||
auto ReplicationClientInfo() const -> ReplClientInfo;
|
||||
|
||||
auto SetCallbacks(HealthCheckCallback succ_cb, HealthCheckCallback fail_cb) -> void;
|
||||
|
||||
auto RpcClient() -> rpc::Client & { return rpc_client_; }
|
||||
|
||||
auto InstanceDownTimeoutSec() const -> std::chrono::seconds;
|
||||
|
||||
friend bool operator==(CoordinatorClient const &first, CoordinatorClient const &second) {
|
||||
return first.config_ == second.config_;
|
||||
}
|
||||
|
||||
@@ -11,17 +11,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#ifdef MG_ENTERPRISE
|
||||
namespace memgraph::coordination {
|
||||
|
||||
namespace memgraph::utils {
|
||||
struct CoordinatorClusterConfig {
|
||||
static constexpr int alive_response_time_difference_sec_{5};
|
||||
};
|
||||
|
||||
template <class F, class T, class R = typename std::result_of<F(T)>::type, class V = std::vector<R>>
|
||||
V fmap(F &&f, const std::vector<T> &v) {
|
||||
V r;
|
||||
r.reserve(v.size());
|
||||
std::ranges::transform(v, std::back_inserter(r), std::forward<F>(f));
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace memgraph::utils
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -28,8 +28,7 @@ struct CoordinatorClientConfig {
|
||||
std::string instance_name;
|
||||
std::string ip_address;
|
||||
uint16_t port{};
|
||||
std::chrono::seconds instance_health_check_frequency_sec{1};
|
||||
std::chrono::seconds instance_down_timeout_sec{5};
|
||||
std::chrono::seconds health_check_frequency_sec{1};
|
||||
|
||||
auto SocketAddress() const -> std::string { return ip_address + ":" + std::to_string(port); }
|
||||
|
||||
|
||||
@@ -72,16 +72,5 @@ class RaftCouldNotFindEntryException final : public utils::BasicException {
|
||||
SPECIALIZE_GET_EXCEPTION_NAME(RaftCouldNotFindEntryException)
|
||||
};
|
||||
|
||||
class RaftCouldNotParseFlagsException final : public utils::BasicException {
|
||||
public:
|
||||
explicit RaftCouldNotParseFlagsException(std::string_view what) noexcept : BasicException(what) {}
|
||||
|
||||
template <class... Args>
|
||||
explicit RaftCouldNotParseFlagsException(fmt::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
: RaftCouldNotParseFlagsException(fmt::format(fmt, std::forward<Args>(args)...)) {}
|
||||
|
||||
SPECIALIZE_GET_EXCEPTION_NAME(RaftCouldNotParseFlagsException)
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
|
||||
@@ -33,11 +33,6 @@ class CoordinatorHandlers {
|
||||
slk::Builder *res_builder);
|
||||
static void SwapMainUUIDHandler(replication::ReplicationHandler &replication_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
|
||||
static void UnregisterReplicaHandler(replication::ReplicationHandler &replication_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
static void EnableWritingOnMainHandler(replication::ReplicationHandler &replication_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "coordination/instance_status.hpp"
|
||||
#include "coordination/raft_state.hpp"
|
||||
#include "coordination/raft_instance.hpp"
|
||||
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||
#include "coordination/replication_instance.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
@@ -30,7 +30,6 @@ class CoordinatorInstance {
|
||||
CoordinatorInstance();
|
||||
|
||||
[[nodiscard]] auto RegisterReplicationInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
|
||||
[[nodiscard]] auto UnregisterReplicationInstance(std::string instance_name) -> UnregisterInstanceCoordinatorStatus;
|
||||
|
||||
[[nodiscard]] auto SetReplicationInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
|
||||
|
||||
@@ -40,11 +39,9 @@ class CoordinatorInstance {
|
||||
|
||||
auto AddCoordinatorInstance(uint32_t raft_server_id, uint32_t raft_port, std::string raft_address) -> void;
|
||||
|
||||
auto GetMainUUID() const -> utils::UUID;
|
||||
|
||||
auto SetMainUUID(utils::UUID new_uuid) -> void;
|
||||
|
||||
private:
|
||||
auto ClusterHasAliveMain_() const -> bool;
|
||||
|
||||
HealthCheckCallback main_succ_cb_, main_fail_cb_, replica_succ_cb_, replica_fail_cb_;
|
||||
|
||||
// NOTE: Must be std::list because we rely on pointer stability
|
||||
@@ -53,7 +50,7 @@ class CoordinatorInstance {
|
||||
|
||||
utils::UUID main_uuid_;
|
||||
|
||||
RaftState raft_state_;
|
||||
RaftInstance self_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
@@ -82,60 +82,6 @@ struct DemoteMainToReplicaRes {
|
||||
|
||||
using DemoteMainToReplicaRpc = rpc::RequestResponse<DemoteMainToReplicaReq, DemoteMainToReplicaRes>;
|
||||
|
||||
struct UnregisterReplicaReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(UnregisterReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(UnregisterReplicaReq const &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit UnregisterReplicaReq(std::string instance_name) : instance_name(std::move(instance_name)) {}
|
||||
|
||||
UnregisterReplicaReq() = default;
|
||||
|
||||
std::string instance_name;
|
||||
};
|
||||
|
||||
struct UnregisterReplicaRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(UnregisterReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const UnregisterReplicaRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit UnregisterReplicaRes(bool success) : success(success) {}
|
||||
UnregisterReplicaRes() = default;
|
||||
|
||||
bool success;
|
||||
};
|
||||
|
||||
using UnregisterReplicaRpc = rpc::RequestResponse<UnregisterReplicaReq, UnregisterReplicaRes>;
|
||||
|
||||
struct EnableWritingOnMainReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(EnableWritingOnMainReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(EnableWritingOnMainReq const &self, memgraph::slk::Builder *builder);
|
||||
|
||||
EnableWritingOnMainReq() = default;
|
||||
};
|
||||
|
||||
struct EnableWritingOnMainRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(EnableWritingOnMainRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(EnableWritingOnMainRes const &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit EnableWritingOnMainRes(bool success) : success(success) {}
|
||||
EnableWritingOnMainRes() = default;
|
||||
|
||||
bool success;
|
||||
};
|
||||
|
||||
using EnableWritingOnMainRpc = rpc::RequestResponse<EnableWritingOnMainReq, EnableWritingOnMainRes>;
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
// SLK serialization declarations
|
||||
@@ -153,14 +99,6 @@ void Load(memgraph::coordination::DemoteMainToReplicaRes *self, memgraph::slk::R
|
||||
void Save(const memgraph::coordination::DemoteMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::coordination::DemoteMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
// UnregisterReplicaRpc
|
||||
void Save(memgraph::coordination::UnregisterReplicaRes const &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::coordination::UnregisterReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
void Save(memgraph::coordination::UnregisterReplicaReq const &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::coordination::UnregisterReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(memgraph::coordination::EnableWritingOnMainRes const &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::coordination::EnableWritingOnMainRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ class CoordinatorState {
|
||||
CoordinatorState &operator=(CoordinatorState &&) noexcept = delete;
|
||||
|
||||
[[nodiscard]] auto RegisterReplicationInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
|
||||
[[nodiscard]] auto UnregisterReplicationInstance(std::string instance_name) -> UnregisterInstanceCoordinatorStatus;
|
||||
|
||||
[[nodiscard]] auto SetReplicationInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
|
||||
|
||||
|
||||
@@ -32,22 +32,15 @@ using nuraft::state_machine;
|
||||
using nuraft::state_mgr;
|
||||
using raft_result = nuraft::cmd_result<ptr<buffer>>;
|
||||
|
||||
class RaftState {
|
||||
private:
|
||||
explicit RaftState(BecomeLeaderCb become_leader_cb, BecomeFollowerCb become_follower_cb, uint32_t raft_server_id,
|
||||
uint32_t raft_port, std::string raft_address);
|
||||
|
||||
auto InitRaftServer() -> void;
|
||||
|
||||
class RaftInstance {
|
||||
public:
|
||||
RaftState() = delete;
|
||||
RaftState(RaftState const &other) = default;
|
||||
RaftState &operator=(RaftState const &other) = default;
|
||||
RaftState(RaftState &&other) noexcept = default;
|
||||
RaftState &operator=(RaftState &&other) noexcept = default;
|
||||
~RaftState();
|
||||
RaftInstance(BecomeLeaderCb become_leader_cb, BecomeFollowerCb become_follower_cb);
|
||||
|
||||
static auto MakeRaftState(BecomeLeaderCb become_leader_cb, BecomeFollowerCb become_follower_cb) -> RaftState;
|
||||
RaftInstance(RaftInstance const &other) = delete;
|
||||
RaftInstance &operator=(RaftInstance const &other) = delete;
|
||||
RaftInstance(RaftInstance &&other) noexcept = delete;
|
||||
RaftInstance &operator=(RaftInstance &&other) noexcept = delete;
|
||||
~RaftInstance();
|
||||
|
||||
auto InstanceName() const -> std::string;
|
||||
auto RaftSocketAddress() const -> std::string;
|
||||
@@ -60,17 +53,18 @@ class RaftState {
|
||||
|
||||
auto AppendRegisterReplicationInstance(std::string const &instance) -> ptr<raft_result>;
|
||||
|
||||
// TODO: (andi) I think variables below can be abstracted
|
||||
uint32_t raft_server_id_;
|
||||
uint32_t raft_port_;
|
||||
std::string raft_address_;
|
||||
|
||||
private:
|
||||
ptr<state_machine> state_machine_;
|
||||
ptr<state_mgr> state_manager_;
|
||||
ptr<raft_server> raft_server_;
|
||||
ptr<logger> logger_;
|
||||
raft_launcher launcher_;
|
||||
|
||||
// TODO: (andi) I think variables below can be abstracted
|
||||
uint32_t raft_server_id_;
|
||||
uint32_t raft_port_;
|
||||
std::string raft_address_;
|
||||
|
||||
BecomeLeaderCb become_leader_cb_;
|
||||
BecomeFollowerCb become_follower_cb_;
|
||||
};
|
||||
@@ -28,15 +28,6 @@ enum class RegisterInstanceCoordinatorStatus : uint8_t {
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum class UnregisterInstanceCoordinatorStatus : uint8_t {
|
||||
NO_INSTANCE_WITH_NAME,
|
||||
IS_MAIN,
|
||||
NOT_COORDINATOR,
|
||||
NOT_LEADER,
|
||||
RPC_FAILED,
|
||||
SUCCESS,
|
||||
};
|
||||
|
||||
enum class SetInstanceToMainCoordinatorStatus : uint8_t {
|
||||
NO_INSTANCE_WITH_NAME,
|
||||
NOT_COORDINATOR,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_client.hpp"
|
||||
#include "coordination/coordinator_cluster_config.hpp"
|
||||
#include "coordination/coordinator_exceptions.hpp"
|
||||
#include "replication_coordination_glue/role.hpp"
|
||||
|
||||
@@ -57,19 +58,11 @@ class ReplicationInstance {
|
||||
|
||||
auto ReplicationClientInfo() const -> ReplClientInfo;
|
||||
|
||||
auto EnsureReplicaHasCorrectMainUUID(utils::UUID const &curr_main_uuid) -> bool;
|
||||
|
||||
auto SendSwapAndUpdateUUID(const utils::UUID &new_main_uuid) -> bool;
|
||||
auto SendUnregisterReplicaRpc(std::string const &instance_name) -> bool;
|
||||
|
||||
// TODO: (andi) Inconsistent API
|
||||
auto SendSwapAndUpdateUUID(const utils::UUID &main_uuid) -> bool;
|
||||
auto GetClient() -> CoordinatorClient &;
|
||||
|
||||
auto EnableWritingOnMain() -> bool;
|
||||
|
||||
auto SetNewMainUUID(utils::UUID const &main_uuid) -> void;
|
||||
auto ResetMainUUID() -> void;
|
||||
auto GetMainUUID() const -> const std::optional<utils::UUID> &;
|
||||
void SetNewMainUUID(const std::optional<utils::UUID> &main_uuid = std::nullopt);
|
||||
auto GetMainUUID() -> const std::optional<utils::UUID> &;
|
||||
|
||||
private:
|
||||
CoordinatorClient client_;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/raft_state.hpp"
|
||||
#include "coordination/raft_instance.hpp"
|
||||
|
||||
#include "coordination/coordinator_exceptions.hpp"
|
||||
#include "nuraft/coordinator_state_machine.hpp"
|
||||
@@ -31,19 +31,19 @@ using nuraft::raft_server;
|
||||
using nuraft::srv_config;
|
||||
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_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_address_ + ":" + std::to_string(raft_port_))),
|
||||
logger_(nullptr),
|
||||
RaftInstance::RaftInstance(BecomeLeaderCb become_leader_cb, BecomeFollowerCb become_follower_cb)
|
||||
: raft_server_id_(FLAGS_raft_server_id),
|
||||
raft_port_(FLAGS_raft_server_port),
|
||||
raft_address_("127.0.0.1"),
|
||||
become_leader_cb_(std::move(become_leader_cb)),
|
||||
become_follower_cb_(std::move(become_follower_cb)) {}
|
||||
become_follower_cb_(std::move(become_follower_cb)) {
|
||||
auto raft_endpoint = raft_address_ + ":" + std::to_string(raft_port_);
|
||||
state_manager_ = cs_new<CoordinatorStateManager>(raft_server_id_, raft_endpoint);
|
||||
state_machine_ = cs_new<CoordinatorStateMachine>();
|
||||
logger_ = nullptr;
|
||||
|
||||
// TODO: (andi) Maybe params file
|
||||
|
||||
auto RaftState::InitRaftServer() -> void {
|
||||
asio_service::options asio_opts;
|
||||
asio_opts.thread_pool_size_ = 1; // TODO: (andi) Improve this
|
||||
|
||||
@@ -70,49 +70,33 @@ auto RaftState::InitRaftServer() -> void {
|
||||
return CbReturnCode::Ok;
|
||||
};
|
||||
|
||||
raft_launcher launcher;
|
||||
|
||||
raft_server_ = launcher.init(state_machine_, state_manager_, logger_, static_cast<int>(raft_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_address_, raft_port_);
|
||||
throw RaftServerStartException("Failed to launch raft server on {}", raft_endpoint);
|
||||
}
|
||||
|
||||
auto maybe_stop = utils::ResettableCounter<20>();
|
||||
do {
|
||||
if (raft_server_->is_initialized()) {
|
||||
return;
|
||||
}
|
||||
while (!raft_server_->is_initialized() && !maybe_stop()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
} while (!maybe_stop());
|
||||
|
||||
throw RaftServerStartException("Failed to initialize raft server on {}:{}", raft_address_, raft_port_);
|
||||
}
|
||||
|
||||
auto RaftState::MakeRaftState(BecomeLeaderCb become_leader_cb, BecomeFollowerCb become_follower_cb) -> RaftState {
|
||||
uint32_t raft_server_id{0};
|
||||
uint32_t raft_port{0};
|
||||
try {
|
||||
raft_server_id = FLAGS_raft_server_id;
|
||||
raft_port = FLAGS_raft_server_port;
|
||||
} catch (std::exception const &e) {
|
||||
throw RaftCouldNotParseFlagsException("Failed to parse flags: {}", e.what());
|
||||
}
|
||||
|
||||
auto raft_state =
|
||||
RaftState(std::move(become_leader_cb), std::move(become_follower_cb), raft_server_id, raft_port, "127.0.0.1");
|
||||
raft_state.InitRaftServer();
|
||||
return raft_state;
|
||||
if (!raft_server_->is_initialized()) {
|
||||
throw RaftServerStartException("Failed to initialize raft server on {}", raft_endpoint);
|
||||
}
|
||||
|
||||
spdlog::info("Raft server started on {}", raft_endpoint);
|
||||
}
|
||||
|
||||
RaftState::~RaftState() { launcher_.shutdown(); }
|
||||
RaftInstance::~RaftInstance() { launcher_.shutdown(); }
|
||||
|
||||
auto RaftState::InstanceName() const -> std::string { return "coordinator_" + std::to_string(raft_server_id_); }
|
||||
auto RaftInstance::InstanceName() const -> std::string { return "coordinator_" + std::to_string(raft_server_id_); }
|
||||
|
||||
auto RaftState::RaftSocketAddress() const -> std::string { return raft_address_ + ":" + std::to_string(raft_port_); }
|
||||
auto RaftInstance::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 raft_address) -> void {
|
||||
auto RaftInstance::AddCoordinatorInstance(uint32_t raft_server_id, uint32_t raft_port, std::string raft_address)
|
||||
-> void {
|
||||
auto const endpoint = raft_address + ":" + std::to_string(raft_port);
|
||||
srv_config const srv_config_to_add(static_cast<int>(raft_server_id), endpoint);
|
||||
if (!raft_server_->add_srv(srv_config_to_add)->get_accepted()) {
|
||||
@@ -121,17 +105,19 @@ auto RaftState::AddCoordinatorInstance(uint32_t raft_server_id, uint32_t raft_po
|
||||
spdlog::info("Request to add server {} to the cluster accepted", endpoint);
|
||||
}
|
||||
|
||||
auto RaftState::GetAllCoordinators() const -> std::vector<ptr<srv_config>> {
|
||||
auto RaftInstance::GetAllCoordinators() const -> std::vector<ptr<srv_config>> {
|
||||
std::vector<ptr<srv_config>> all_srv_configs;
|
||||
raft_server_->get_srv_config_all(all_srv_configs);
|
||||
return all_srv_configs;
|
||||
}
|
||||
|
||||
auto RaftState::IsLeader() const -> bool { return raft_server_->is_leader(); }
|
||||
auto RaftInstance::IsLeader() const -> bool { return raft_server_->is_leader(); }
|
||||
|
||||
auto RaftState::RequestLeadership() -> bool { return raft_server_->is_leader() || raft_server_->request_leadership(); }
|
||||
auto RaftInstance::RequestLeadership() -> bool {
|
||||
return raft_server_->is_leader() || raft_server_->request_leadership();
|
||||
}
|
||||
|
||||
auto RaftState::AppendRegisterReplicationInstance(std::string const &instance) -> ptr<raft_result> {
|
||||
auto RaftInstance::AppendRegisterReplicationInstance(std::string const &instance) -> ptr<raft_result> {
|
||||
auto new_log = CoordinatorStateMachine::EncodeRegisterReplicationInstance(instance);
|
||||
return raft_server_->append_entries({new_log});
|
||||
}
|
||||
@@ -34,8 +34,9 @@ auto ReplicationInstance::OnSuccessPing() -> void {
|
||||
}
|
||||
|
||||
auto ReplicationInstance::OnFailPing() -> bool {
|
||||
auto elapsed_time = std::chrono::system_clock::now() - last_response_time_;
|
||||
is_alive_ = elapsed_time < client_.InstanceDownTimeoutSec();
|
||||
is_alive_ =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - last_response_time_).count() <
|
||||
CoordinatorClusterConfig::alive_response_time_difference_sec_;
|
||||
return is_alive_;
|
||||
}
|
||||
|
||||
@@ -50,14 +51,13 @@ auto ReplicationInstance::IsMain() const -> bool {
|
||||
return replication_role_ == replication_coordination_glue::ReplicationRole::MAIN;
|
||||
}
|
||||
|
||||
auto ReplicationInstance::PromoteToMain(utils::UUID new_uuid, ReplicationClientsInfo repl_clients_info,
|
||||
auto ReplicationInstance::PromoteToMain(utils::UUID uuid, ReplicationClientsInfo repl_clients_info,
|
||||
HealthCheckCallback main_succ_cb, HealthCheckCallback main_fail_cb) -> bool {
|
||||
if (!client_.SendPromoteReplicaToMainRpc(new_uuid, std::move(repl_clients_info))) {
|
||||
if (!client_.SendPromoteReplicaToMainRpc(uuid, std::move(repl_clients_info))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
replication_role_ = replication_coordination_glue::ReplicationRole::MAIN;
|
||||
main_uuid_ = new_uuid;
|
||||
client_.SetCallbacks(std::move(main_succ_cb), std::move(main_fail_cb));
|
||||
|
||||
return true;
|
||||
@@ -85,31 +85,16 @@ auto ReplicationInstance::ReplicationClientInfo() const -> CoordinatorClientConf
|
||||
}
|
||||
|
||||
auto ReplicationInstance::GetClient() -> CoordinatorClient & { return client_; }
|
||||
void ReplicationInstance::SetNewMainUUID(const std::optional<utils::UUID> &main_uuid) { main_uuid_ = main_uuid; }
|
||||
auto ReplicationInstance::GetMainUUID() -> const std::optional<utils::UUID> & { return main_uuid_; }
|
||||
|
||||
auto ReplicationInstance::SetNewMainUUID(utils::UUID const &main_uuid) -> void { main_uuid_ = main_uuid; }
|
||||
auto ReplicationInstance::ResetMainUUID() -> void { main_uuid_ = std::nullopt; }
|
||||
auto ReplicationInstance::GetMainUUID() const -> std::optional<utils::UUID> const & { return main_uuid_; }
|
||||
|
||||
auto ReplicationInstance::EnsureReplicaHasCorrectMainUUID(utils::UUID const &curr_main_uuid) -> bool {
|
||||
if (!main_uuid_ || *main_uuid_ != curr_main_uuid) {
|
||||
return SendSwapAndUpdateUUID(curr_main_uuid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ReplicationInstance::SendSwapAndUpdateUUID(const utils::UUID &new_main_uuid) -> bool {
|
||||
if (!replication_coordination_glue::SendSwapMainUUIDRpc(client_.RpcClient(), new_main_uuid)) {
|
||||
auto ReplicationInstance::SendSwapAndUpdateUUID(const utils::UUID &main_uuid) -> bool {
|
||||
if (!replication_coordination_glue::SendSwapMainUUIDRpc(client_.RpcClient(), main_uuid)) {
|
||||
return false;
|
||||
}
|
||||
SetNewMainUUID(new_main_uuid);
|
||||
SetNewMainUUID(main_uuid_);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ReplicationInstance::SendUnregisterReplicaRpc(std::string const &instance_name) -> bool {
|
||||
return client_.SendUnregisterReplicaRpc(instance_name);
|
||||
}
|
||||
|
||||
auto ReplicationInstance::EnableWritingOnMain() -> bool { return client_.SendEnableWritingOnMainRpc(); }
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
|
||||
@@ -16,4 +16,10 @@ namespace memgraph::dbms {
|
||||
constexpr std::string_view kDefaultDB = "memgraph"; //!< Name of the default database
|
||||
constexpr std::string_view kMultiTenantDir = "databases"; //!< Name of the multi-tenant directory
|
||||
|
||||
#ifdef MG_EXPERIMENTAL_REPLICATION_MULTITENANCY
|
||||
constexpr bool allow_mt_repl = true;
|
||||
#else
|
||||
constexpr bool allow_mt_repl = false;
|
||||
#endif
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -25,11 +25,6 @@ auto CoordinatorHandler::RegisterReplicationInstance(memgraph::coordination::Coo
|
||||
return coordinator_state_.RegisterReplicationInstance(config);
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::UnregisterReplicationInstance(std::string instance_name)
|
||||
-> coordination::UnregisterInstanceCoordinatorStatus {
|
||||
return coordinator_state_.UnregisterReplicationInstance(std::move(instance_name));
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::SetReplicationInstanceToMain(std::string instance_name)
|
||||
-> coordination::SetInstanceToMainCoordinatorStatus {
|
||||
return coordinator_state_.SetReplicationInstanceToMain(std::move(instance_name));
|
||||
|
||||
@@ -28,13 +28,9 @@ class CoordinatorHandler {
|
||||
public:
|
||||
explicit CoordinatorHandler(coordination::CoordinatorState &coordinator_state);
|
||||
|
||||
// TODO: (andi) When moving coordinator state on same instances, rename from RegisterReplicationInstance to
|
||||
// RegisterInstance
|
||||
auto RegisterReplicationInstance(coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterInstanceCoordinatorStatus;
|
||||
|
||||
auto UnregisterReplicationInstance(std::string instance_name) -> coordination::UnregisterInstanceCoordinatorStatus;
|
||||
|
||||
auto SetReplicationInstanceToMain(std::string instance_name) -> coordination::SetInstanceToMainCoordinatorStatus;
|
||||
|
||||
auto ShowInstances() const -> std::vector<coordination::InstanceStatus>;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/global.hpp"
|
||||
#include "flags/experimental.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "system/include/system/system.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
@@ -159,9 +158,9 @@ struct Durability {
|
||||
}
|
||||
};
|
||||
|
||||
DbmsHandler::DbmsHandler(storage::Config config, replication::ReplicationState &repl_state, auth::SynchedAuth &auth,
|
||||
bool recovery_on_startup)
|
||||
: default_config_{std::move(config)}, auth_{auth}, repl_state_{repl_state} {
|
||||
DbmsHandler::DbmsHandler(storage::Config config, memgraph::system::System &system,
|
||||
replication::ReplicationState &repl_state, auth::SynchedAuth &auth, bool recovery_on_startup)
|
||||
: default_config_{std::move(config)}, auth_{auth}, repl_state_{repl_state}, system_{&system} {
|
||||
// TODO: Decouple storage config from dbms config
|
||||
// TODO: Save individual db configs inside the kvstore and restore from there
|
||||
|
||||
@@ -420,10 +419,7 @@ void DbmsHandler::UpdateDurability(const storage::Config &config, std::optional<
|
||||
#endif
|
||||
|
||||
void DbmsHandler::RecoverStorageReplication(DatabaseAccess db_acc, replication::RoleMainData &role_main_data) {
|
||||
using enum memgraph::flags::Experiments;
|
||||
auto const is_enterprise = license::global_license_checker.IsEnterpriseValidFast();
|
||||
auto experimental_system_replication = flags::AreExperimentsEnabled(SYSTEM_REPLICATION);
|
||||
if ((is_enterprise && experimental_system_replication) || db_acc->name() == dbms::kDefaultDB) {
|
||||
if (allow_mt_repl || db_acc->name() == dbms::kDefaultDB) {
|
||||
// Handle global replication state
|
||||
spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
|
||||
// RECOVER REPLICA CONNECTIONS
|
||||
|
||||
@@ -107,7 +107,8 @@ class DbmsHandler {
|
||||
* @param auth pointer to the global authenticator
|
||||
* @param recovery_on_startup restore databases (and its content) and authentication data
|
||||
*/
|
||||
DbmsHandler(storage::Config config, replication::ReplicationState &repl_state, auth::SynchedAuth &auth,
|
||||
DbmsHandler(storage::Config config, memgraph::system::System &system, replication::ReplicationState &repl_state,
|
||||
auth::SynchedAuth &auth,
|
||||
bool recovery_on_startup); // TODO If more arguments are added use a config struct
|
||||
#else
|
||||
/**
|
||||
@@ -115,8 +116,9 @@ class DbmsHandler {
|
||||
*
|
||||
* @param configs storage configuration
|
||||
*/
|
||||
DbmsHandler(storage::Config config, replication::ReplicationState &repl_state)
|
||||
DbmsHandler(storage::Config config, memgraph::system::System &system, replication::ReplicationState &repl_state)
|
||||
: repl_state_{repl_state},
|
||||
system_{&system},
|
||||
db_gatekeeper_{[&] {
|
||||
config.salient.name = kDefaultDB;
|
||||
return std::move(config);
|
||||
@@ -270,20 +272,6 @@ class DbmsHandler {
|
||||
// coordination::CoordinatorState &CoordinatorState() { return coordinator_state_; }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return all active databases.
|
||||
*
|
||||
* @return std::vector<std::string>
|
||||
*/
|
||||
auto Count() const -> std::size_t {
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::shared_lock<LockT> rd(lock_);
|
||||
return db_handler_.size();
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the statistics all databases.
|
||||
*
|
||||
@@ -599,6 +587,9 @@ class DbmsHandler {
|
||||
// current replication role. TODO: make Database Access explicit about the role and remove this from
|
||||
// dbms stuff
|
||||
replication::ReplicationState &repl_state_; //!< Ref to global replication state
|
||||
public:
|
||||
// TODO fix to be non public/remove from dbms....maybe
|
||||
system::System *system_;
|
||||
|
||||
#ifndef MG_ENTERPRISE
|
||||
mutable utils::Gatekeeper<Database> db_gatekeeper_; //!< Single databases gatekeeper
|
||||
|
||||
@@ -144,8 +144,6 @@ class Handler {
|
||||
auto cbegin() const { return items_.cbegin(); }
|
||||
auto cend() const { return items_.cend(); }
|
||||
|
||||
auto size() const { return items_.size(); }
|
||||
|
||||
struct string_hash {
|
||||
using is_transparent = void;
|
||||
[[nodiscard]] size_t operator()(const char *s) const { return std::hash<std::string_view>{}(s); }
|
||||
|
||||
@@ -155,12 +155,6 @@ void InMemoryReplicationHandlers::HeartbeatHandler(dbms::DbmsHandler *dbms_handl
|
||||
return;
|
||||
}
|
||||
// TODO: this handler is agnostic of InMemory, move to be reused by on-disk
|
||||
if (!db_acc.has_value()) {
|
||||
spdlog::warn("No database accessor");
|
||||
storage::replication::HeartbeatRes res{false, 0, ""};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
auto const *storage = db_acc->get()->storage();
|
||||
storage::replication::HeartbeatRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load(),
|
||||
std::string{storage->repl_storage_state_.epoch_.id()}};
|
||||
@@ -469,6 +463,7 @@ void InMemoryReplicationHandlers::TimestampHandler(dbms::DbmsHandler *dbms_handl
|
||||
slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
/////// AF how does this work, does it get all deltas at once or what?
|
||||
uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage *storage,
|
||||
storage::durability::BaseDecoder *decoder,
|
||||
const uint64_t version) {
|
||||
|
||||
0
src/dbms/replication_client.cpp
Normal file
0
src/dbms/replication_client.cpp
Normal file
0
src/dbms/replication_handler.cpp
Normal file
0
src/dbms/replication_handler.cpp
Normal file
0
src/dbms/replication_handler.hpp
Normal file
0
src/dbms/replication_handler.hpp
Normal file
@@ -57,7 +57,6 @@ namespace slk {
|
||||
// Serialize code for CreateDatabaseReq
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.main_uuid, builder);
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
@@ -65,7 +64,6 @@ void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgrap
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->main_uuid, reader);
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
@@ -89,7 +87,6 @@ void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk
|
||||
// Serialize code for DropDatabaseReq
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.main_uuid, builder);
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
@@ -97,7 +94,6 @@ void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph:
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->main_uuid, reader);
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
add_library(mg-flags STATIC
|
||||
audit.cpp
|
||||
bolt.cpp
|
||||
general.cpp
|
||||
isolation_level.cpp
|
||||
log_level.cpp
|
||||
memory_limit.cpp
|
||||
run_time_configurable.cpp
|
||||
storage_mode.cpp
|
||||
query.cpp
|
||||
replication.cpp
|
||||
experimental.cpp
|
||||
experimental.hpp)
|
||||
target_include_directories(mg-flags PUBLIC include)
|
||||
target_link_libraries(mg-flags
|
||||
PUBLIC spdlog::spdlog mg-settings mg-utils
|
||||
PRIVATE lib::rangev3)
|
||||
add_library(mg-flags STATIC audit.cpp
|
||||
bolt.cpp
|
||||
general.cpp
|
||||
isolation_level.cpp
|
||||
log_level.cpp
|
||||
memory_limit.cpp
|
||||
run_time_configurable.cpp
|
||||
storage_mode.cpp
|
||||
query.cpp
|
||||
replication.cpp)
|
||||
target_include_directories(mg-flags PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(mg-flags PUBLIC spdlog::spdlog mg-settings mg-utils)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "flags/audit.hpp"
|
||||
#include "flags/bolt.hpp"
|
||||
#include "flags/experimental.hpp"
|
||||
#include "flags/general.hpp"
|
||||
#include "flags/isolation_level.hpp"
|
||||
#include "flags/log_level.hpp"
|
||||
|
||||
@@ -1,67 +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 "flags/experimental.hpp"
|
||||
#include "range/v3/all.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
|
||||
// Bolt server flags.
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(experimental_enabled, "",
|
||||
"Experimental features to be used, comma seperated. Options [system-replication]");
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace memgraph::flags {
|
||||
|
||||
auto const mapping = std::map{std::pair{"system-replication"sv, Experiments::SYSTEM_REPLICATION}};
|
||||
|
||||
auto ExperimentsInstance() -> Experiments & {
|
||||
static auto instance = Experiments{};
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool AreExperimentsEnabled(Experiments experiments) {
|
||||
using t = std::underlying_type_t<Experiments>;
|
||||
|
||||
auto actual = static_cast<t>(ExperimentsInstance());
|
||||
auto check = static_cast<t>(experiments);
|
||||
|
||||
return (actual & check) == check;
|
||||
}
|
||||
|
||||
void InitializeExperimental() {
|
||||
namespace rv = ranges::views;
|
||||
|
||||
auto const connonicalize_string = [](auto &&rng) {
|
||||
auto const is_space = [](auto c) { return c == ' '; };
|
||||
auto const to_lower = [](unsigned char c) { return std::tolower(c); };
|
||||
|
||||
return rng | rv::drop_while(is_space) | rv::take_while(std::not_fn(is_space)) | rv::transform(to_lower) |
|
||||
ranges::to<std::string>;
|
||||
};
|
||||
|
||||
auto const mapping_end = mapping.cend();
|
||||
using underlying_type = std::underlying_type_t<Experiments>;
|
||||
auto to_set = underlying_type{};
|
||||
for (auto &&experiment : FLAGS_experimental_enabled | rv::split(',') | rv::transform(connonicalize_string)) {
|
||||
if (auto it = mapping.find(experiment); it != mapping_end) {
|
||||
to_set |= static_cast<underlying_type>(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
ExperimentsInstance() = static_cast<Experiments>(to_set);
|
||||
}
|
||||
|
||||
} // namespace memgraph::flags
|
||||
@@ -1,32 +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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
|
||||
// Short help flag.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_string(experimental_enabled);
|
||||
|
||||
namespace memgraph::flags {
|
||||
|
||||
// Each bit is an enabled experiment
|
||||
// old experiments can be reused once code cleanup has happened
|
||||
enum class Experiments : uint8_t {
|
||||
SYSTEM_REPLICATION = 1 << 0,
|
||||
};
|
||||
|
||||
bool AreExperimentsEnabled(Experiments experiments);
|
||||
|
||||
void InitializeExperimental();
|
||||
|
||||
} // namespace memgraph::flags
|
||||
@@ -18,10 +18,6 @@ DEFINE_uint32(coordinator_server_port, 0, "Port on which coordinator servers wil
|
||||
DEFINE_uint32(raft_server_port, 0, "Port on which raft servers will be started.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint32(raft_server_id, 0, "Unique ID of the raft server.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint32(instance_down_timeout_sec, 5, "Time duration after which an instance is considered down.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint32(instance_health_check_frequency_sec, 1, "The time duration between two health checks/pings.");
|
||||
#endif
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -20,10 +20,6 @@ DECLARE_uint32(coordinator_server_port);
|
||||
DECLARE_uint32(raft_server_port);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_uint32(raft_server_id);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_uint32(instance_down_timeout_sec);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_uint32(instance_health_check_frequency_sec);
|
||||
#endif
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -134,7 +134,6 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
memgraph::flags::InitializeLogger();
|
||||
memgraph::flags::InitializeExperimental();
|
||||
|
||||
// Unhandled exception handler init.
|
||||
std::set_terminate(&memgraph::utils::TerminateHandler);
|
||||
@@ -356,10 +355,6 @@ int main(int argc, char **argv) {
|
||||
memgraph::query::InterpreterConfig interp_config{
|
||||
.query = {.allow_load_csv = FLAGS_allow_load_csv},
|
||||
.replication_replica_check_frequency = std::chrono::seconds(FLAGS_replication_replica_check_frequency_sec),
|
||||
#ifdef MG_ENTERPRISE
|
||||
.instance_down_timeout_sec = std::chrono::seconds(FLAGS_instance_down_timeout_sec),
|
||||
.instance_health_check_frequency_sec = std::chrono::seconds(FLAGS_instance_health_check_frequency_sec),
|
||||
#endif
|
||||
.default_kafka_bootstrap_servers = FLAGS_kafka_bootstrap_servers,
|
||||
.default_pulsar_service_url = FLAGS_pulsar_service_url,
|
||||
.stream_transaction_conflict_retries = FLAGS_stream_transaction_conflict_retries,
|
||||
@@ -401,7 +396,7 @@ int main(int argc, char **argv) {
|
||||
memgraph::coordination::CoordinatorState coordinator_state;
|
||||
#endif
|
||||
|
||||
memgraph::dbms::DbmsHandler dbms_handler(db_config, repl_state
|
||||
memgraph::dbms::DbmsHandler dbms_handler(db_config, system, repl_state
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
auth_, FLAGS_data_recovery_on_startup
|
||||
@@ -414,7 +409,7 @@ int main(int argc, char **argv) {
|
||||
auto replication_handler = memgraph::replication::ReplicationHandler{repl_state, dbms_handler
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
system, auth_
|
||||
&system, auth_
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -60,11 +60,10 @@ void *my_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size, size_t
|
||||
unsigned arena_ind) {
|
||||
// This needs to be before, to throw exception in case of too big alloc
|
||||
if (*commit) [[likely]] {
|
||||
memgraph::utils::total_memory_tracker.Alloc(static_cast<int64_t>(size));
|
||||
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
|
||||
GetQueriesMemoryControl().TrackAllocOnCurrentThread(size);
|
||||
}
|
||||
// This needs to be here so it doesn't get incremented in case the first TrackAlloc throws an exception
|
||||
memgraph::utils::total_memory_tracker.Alloc(static_cast<int64_t>(size));
|
||||
}
|
||||
|
||||
auto *ptr = old_hooks->alloc(extent_hooks, new_addr, size, alignment, zero, commit, arena_ind);
|
||||
@@ -118,10 +117,10 @@ static bool my_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, siz
|
||||
return err;
|
||||
}
|
||||
|
||||
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
|
||||
GetQueriesMemoryControl().TrackAllocOnCurrentThread(length);
|
||||
}
|
||||
memgraph::utils::total_memory_tracker.Alloc(static_cast<int64_t>(length));
|
||||
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
|
||||
GetQueriesMemoryControl().TrackAllocOnCurrentThread(size);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ void QueriesMemoryControl::CreateTransactionIdTracker(uint64_t transaction_id, s
|
||||
|
||||
bool QueriesMemoryControl::EraseTransactionIdTracker(uint64_t transaction_id) {
|
||||
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
|
||||
auto removed = transaction_id_to_tracker_accessor.remove(transaction_id);
|
||||
auto removed = transaction_id_to_tracker.access().remove(transaction_id);
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,6 @@ struct InterpreterConfig {
|
||||
// The same as \ref memgraph::replication::ReplicationClientConfig
|
||||
std::chrono::seconds replication_replica_check_frequency{1};
|
||||
|
||||
std::chrono::seconds instance_down_timeout_sec{5};
|
||||
std::chrono::seconds instance_health_check_frequency_sec{1};
|
||||
|
||||
std::string default_kafka_bootstrap_servers;
|
||||
std::string default_pulsar_service_url;
|
||||
uint32_t stream_transaction_conflict_retries;
|
||||
|
||||
@@ -3034,7 +3034,7 @@ class ReplicationQuery : public memgraph::query::Query {
|
||||
|
||||
enum class SyncMode { SYNC, ASYNC };
|
||||
|
||||
enum class ReplicaState { READY, REPLICATING, RECOVERY, MAYBE_BEHIND, DIVERGED_FROM_MAIN };
|
||||
enum class ReplicaState { READY, REPLICATING, RECOVERY, MAYBE_BEHIND, UNREACHABLE };
|
||||
|
||||
ReplicationQuery() = default;
|
||||
|
||||
@@ -3071,13 +3071,7 @@ class CoordinatorQuery : public memgraph::query::Query {
|
||||
static const utils::TypeInfo kType;
|
||||
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
|
||||
|
||||
enum class Action {
|
||||
REGISTER_INSTANCE,
|
||||
UNREGISTER_INSTANCE,
|
||||
SET_INSTANCE_TO_MAIN,
|
||||
SHOW_INSTANCES,
|
||||
ADD_COORDINATOR_INSTANCE
|
||||
};
|
||||
enum class Action { REGISTER_INSTANCE, SET_INSTANCE_TO_MAIN, SHOW_INSTANCES, ADD_COORDINATOR_INSTANCE };
|
||||
|
||||
enum class SyncMode { SYNC, ASYNC };
|
||||
|
||||
|
||||
@@ -399,14 +399,6 @@ antlrcpp::Any CypherMainVisitor::visitRegisterInstanceOnCoordinator(
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitUnregisterInstanceOnCoordinator(
|
||||
MemgraphCypher::UnregisterInstanceOnCoordinatorContext *ctx) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::UNREGISTER_INSTANCE;
|
||||
coordinator_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitAddCoordinatorInstance(MemgraphCypher::AddCoordinatorInstanceContext *ctx) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
|
||||
@@ -475,10 +467,8 @@ antlrcpp::Any CypherMainVisitor::visitLoadCsv(MemgraphCypher::LoadCsvContext *ct
|
||||
|
||||
auto *load_csv = storage_->Create<LoadCsv>();
|
||||
// handle file name
|
||||
if (ctx->csvFile()->literal() && ctx->csvFile()->literal()->StringLiteral()) {
|
||||
if (ctx->csvFile()->literal()->StringLiteral()) {
|
||||
load_csv->file_ = std::any_cast<Expression *>(ctx->csvFile()->accept(this));
|
||||
} else if (ctx->csvFile()->parameter()) {
|
||||
load_csv->file_ = std::any_cast<ParameterLookup *>(ctx->csvFile()->accept(this));
|
||||
} else {
|
||||
throw SemanticException("CSV file path should be a string literal");
|
||||
}
|
||||
|
||||
@@ -243,12 +243,6 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitRegisterInstanceOnCoordinator(MemgraphCypher::RegisterInstanceOnCoordinatorContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitUnregisterInstanceOnCoordinator(
|
||||
MemgraphCypher::UnregisterInstanceOnCoordinatorContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
|
||||
@@ -190,7 +190,6 @@ replicationQuery : setReplicationRole
|
||||
;
|
||||
|
||||
coordinatorQuery : registerInstanceOnCoordinator
|
||||
| unregisterInstanceOnCoordinator
|
||||
| setInstanceToMain
|
||||
| showInstances
|
||||
| addCoordinatorInstance
|
||||
@@ -266,7 +265,7 @@ loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
|
||||
( NULLIF nullif ) ?
|
||||
AS rowVar ;
|
||||
|
||||
csvFile : literal | parameter ;
|
||||
csvFile : literal ;
|
||||
|
||||
delimiter : literal ;
|
||||
|
||||
@@ -393,8 +392,6 @@ registerReplica : REGISTER REPLICA instanceName ( SYNC | ASYNC )
|
||||
|
||||
registerInstanceOnCoordinator : REGISTER INSTANCE instanceName ON coordinatorSocketAddress ( AS ASYNC ) ? WITH replicationSocketAddress ;
|
||||
|
||||
unregisterInstanceOnCoordinator : UNREGISTER INSTANCE instanceName ;
|
||||
|
||||
setInstanceToMain : SET INSTANCE instanceName TO MAIN ;
|
||||
|
||||
raftServerId : literal ;
|
||||
|
||||
@@ -141,7 +141,6 @@ TRIGGER : T R I G G E R ;
|
||||
TRIGGERS : T R I G G E R S ;
|
||||
UNCOMMITTED : U N C O M M I T T E D ;
|
||||
UNLOCK : U N L O C K ;
|
||||
UNREGISTER : U N R E G I S T E R ;
|
||||
UPDATE : U P D A T E ;
|
||||
USE : U S E ;
|
||||
USER : U S E R ;
|
||||
|
||||
@@ -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
|
||||
@@ -394,16 +394,9 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
|
||||
// can reference symbols bound later in the same MATCH. We collect them
|
||||
// here, so that they can be checked after visiting Match.
|
||||
scope.identifiers_in_match.emplace_back(&ident);
|
||||
} else if (scope.in_call_subquery && !scope.in_with) {
|
||||
if (!scope.symbols.contains(ident.name_) && !ConsumePredefinedIdentifier(ident.name_)) {
|
||||
throw UnboundVariableError(ident.name_);
|
||||
}
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::ANY);
|
||||
} else {
|
||||
// Everything else references a bound symbol.
|
||||
if (!HasSymbol(ident.name_) && !ConsumePredefinedIdentifier(ident.name_)) {
|
||||
throw UnboundVariableError(ident.name_);
|
||||
}
|
||||
if (!HasSymbol(ident.name_) && !ConsumePredefinedIdentifier(ident.name_)) throw UnboundVariableError(ident.name_);
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::ANY);
|
||||
}
|
||||
ident.MapTo(symbol);
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/functional.hpp"
|
||||
#include "utils/likely.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
@@ -110,7 +109,6 @@
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "coordination/constants.hpp"
|
||||
#include "flags/experimental.hpp"
|
||||
#endif
|
||||
|
||||
namespace memgraph::metrics {
|
||||
@@ -439,8 +437,8 @@ class ReplQueryHandler {
|
||||
case storage::replication::ReplicaState::MAYBE_BEHIND:
|
||||
replica.state = ReplicationQuery::ReplicaState::MAYBE_BEHIND;
|
||||
break;
|
||||
case storage::replication::ReplicaState::DIVERGED_FROM_MAIN:
|
||||
replica.state = ReplicationQuery::ReplicaState::DIVERGED_FROM_MAIN;
|
||||
case storage::replication::ReplicaState::UNREACHABLE:
|
||||
replica.state = ReplicationQuery::ReplicaState::UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -462,32 +460,11 @@ class CoordQueryHandler final : public query::CoordinatorQueryHandler {
|
||||
|
||||
: coordinator_handler_(coordinator_state) {}
|
||||
|
||||
void UnregisterInstance(std::string const &instance_name) override {
|
||||
auto status = coordinator_handler_.UnregisterReplicationInstance(instance_name);
|
||||
switch (status) {
|
||||
using enum memgraph::coordination::UnregisterInstanceCoordinatorStatus;
|
||||
case NO_INSTANCE_WITH_NAME:
|
||||
throw QueryRuntimeException("No instance with such name!");
|
||||
case IS_MAIN:
|
||||
throw QueryRuntimeException(
|
||||
"Alive main instance can't be unregistered! Shut it down to trigger failover and then unregister it!");
|
||||
case NOT_COORDINATOR:
|
||||
throw QueryRuntimeException("UNREGISTER INSTANCE query can only be run on a coordinator!");
|
||||
case NOT_LEADER:
|
||||
throw QueryRuntimeException("Couldn't unregister replica instance since coordinator is not a leader!");
|
||||
case RPC_FAILED:
|
||||
throw QueryRuntimeException(
|
||||
"Couldn't unregister replica instance because current main instance couldn't unregister replica!");
|
||||
case SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterReplicationInstance(std::string const &coordinator_socket_address,
|
||||
std::string const &replication_socket_address,
|
||||
std::chrono::seconds const &instance_check_frequency,
|
||||
std::chrono::seconds const &instance_down_timeout, std::string const &instance_name,
|
||||
CoordinatorQuery::SyncMode sync_mode) override {
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
void RegisterReplicationInstance(const std::string &coordinator_socket_address,
|
||||
const std::string &replication_socket_address,
|
||||
const std::chrono::seconds instance_check_frequency,
|
||||
const std::string &instance_name, CoordinatorQuery::SyncMode sync_mode) override {
|
||||
const auto maybe_replication_ip_port =
|
||||
io::network::Endpoint::ParseSocketOrAddress(replication_socket_address, std::nullopt);
|
||||
if (!maybe_replication_ip_port) {
|
||||
@@ -512,8 +489,7 @@ class CoordQueryHandler final : public query::CoordinatorQueryHandler {
|
||||
coordination::CoordinatorClientConfig{.instance_name = instance_name,
|
||||
.ip_address = coordinator_server_ip,
|
||||
.port = coordinator_server_port,
|
||||
.instance_health_check_frequency_sec = instance_check_frequency,
|
||||
.instance_down_timeout_sec = instance_down_timeout,
|
||||
.health_check_frequency_sec = instance_check_frequency,
|
||||
.replication_client_info = repl_config,
|
||||
.ssl = std::nullopt};
|
||||
|
||||
@@ -1109,8 +1085,8 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
case ReplicationQuery::ReplicaState::MAYBE_BEHIND:
|
||||
typed_replica.emplace_back("invalid");
|
||||
break;
|
||||
case ReplicationQuery::ReplicaState::DIVERGED_FROM_MAIN:
|
||||
typed_replica.emplace_back("diverged");
|
||||
case ReplicationQuery::ReplicaState::UNREACHABLE:
|
||||
typed_replica.emplace_back("unreachable");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1181,15 +1157,12 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
auto coordinator_socket_address_tv = coordinator_query->coordinator_socket_address_->Accept(evaluator);
|
||||
auto replication_socket_address_tv = coordinator_query->replication_socket_address_->Accept(evaluator);
|
||||
callback.fn = [handler = CoordQueryHandler{*coordinator_state}, coordinator_socket_address_tv,
|
||||
replication_socket_address_tv,
|
||||
instance_health_check_frequency_sec = config.instance_health_check_frequency_sec,
|
||||
replication_socket_address_tv, main_check_frequency = config.replication_replica_check_frequency,
|
||||
instance_name = coordinator_query->instance_name_,
|
||||
instance_down_timeout_sec = config.instance_down_timeout_sec,
|
||||
sync_mode = coordinator_query->sync_mode_]() mutable {
|
||||
handler.RegisterReplicationInstance(std::string(coordinator_socket_address_tv.ValueString()),
|
||||
std::string(replication_socket_address_tv.ValueString()),
|
||||
instance_health_check_frequency_sec, instance_down_timeout_sec,
|
||||
instance_name, sync_mode);
|
||||
main_check_frequency, instance_name, sync_mode);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
|
||||
@@ -1199,30 +1172,6 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
coordinator_socket_address_tv.ValueString(), coordinator_query->instance_name_));
|
||||
return callback;
|
||||
}
|
||||
case CoordinatorQuery::Action::UNREGISTER_INSTANCE:
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
throw QueryException("Trying to use enterprise feature without a valid license.");
|
||||
}
|
||||
|
||||
if constexpr (!coordination::allow_ha) {
|
||||
throw QueryRuntimeException(
|
||||
"High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to "
|
||||
"be able to use this functionality.");
|
||||
}
|
||||
if (!FLAGS_raft_server_id) {
|
||||
throw QueryRuntimeException("Only coordinator can register coordinator server!");
|
||||
}
|
||||
callback.fn = [handler = CoordQueryHandler{*coordinator_state},
|
||||
instance_name = coordinator_query->instance_name_]() mutable {
|
||||
handler.UnregisterInstance(instance_name);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
notifications->emplace_back(
|
||||
SeverityLevel::INFO, NotificationCode::UNREGISTER_INSTANCE,
|
||||
fmt::format("Coordinator has unregistered instance {}.", coordinator_query->instance_name_));
|
||||
|
||||
return callback;
|
||||
|
||||
case CoordinatorQuery::Action::SET_INSTANCE_TO_MAIN: {
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
throw QueryException("Trying to use enterprise feature without a valid license.");
|
||||
@@ -1265,13 +1214,17 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
callback.fn = [handler = CoordQueryHandler{*coordinator_state},
|
||||
replica_nfields = callback.header.size()]() mutable {
|
||||
auto const instances = handler.ShowInstances();
|
||||
auto const converter = [](const auto &status) -> std::vector<TypedValue> {
|
||||
return {TypedValue{status.instance_name}, TypedValue{status.raft_socket_address},
|
||||
TypedValue{status.coord_socket_address}, TypedValue{status.is_alive},
|
||||
TypedValue{status.cluster_role}};
|
||||
};
|
||||
std::vector<std::vector<TypedValue>> result{};
|
||||
result.reserve(result.size());
|
||||
|
||||
return utils::fmap(converter, instances);
|
||||
std::ranges::transform(instances, std::back_inserter(result),
|
||||
[](const auto &status) -> std::vector<TypedValue> {
|
||||
return {TypedValue{status.instance_name}, TypedValue{status.raft_socket_address},
|
||||
TypedValue{status.coord_socket_address}, TypedValue{status.is_alive},
|
||||
TypedValue{status.cluster_role}};
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
@@ -3934,9 +3887,7 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
|
||||
if (current_db.in_explicit_db_) {
|
||||
throw QueryException("Database switching is prohibited if session explicitly defines the used database");
|
||||
}
|
||||
|
||||
using enum memgraph::flags::Experiments;
|
||||
if (!flags::AreExperimentsEnabled(SYSTEM_REPLICATION) && is_replica) {
|
||||
if (!dbms::allow_mt_repl && is_replica) {
|
||||
throw QueryException("Query forbidden on the replica!");
|
||||
}
|
||||
return PreparedQuery{{"STATUS"},
|
||||
@@ -4401,19 +4352,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
|
||||
UpdateTypeCount(rw_type);
|
||||
|
||||
bool const write_query = IsQueryWrite(rw_type);
|
||||
if (write_query) {
|
||||
if (interpreter_context_->repl_state->IsReplica()) {
|
||||
query_execution = nullptr;
|
||||
throw QueryException("Write query forbidden on the replica!");
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (FLAGS_coordinator_server_port && !interpreter_context_->repl_state->IsMainWriteable()) {
|
||||
query_execution = nullptr;
|
||||
throw QueryException(
|
||||
"Write query forbidden on the main! Coordinator needs to enable writing on main by sending RPC message.");
|
||||
}
|
||||
#endif
|
||||
if (interpreter_context_->repl_state->IsReplica() && IsQueryWrite(rw_type)) {
|
||||
query_execution = nullptr;
|
||||
throw QueryException("Write query forbidden on the replica!");
|
||||
}
|
||||
|
||||
// Set the target db to the current db (some queries have different target from the current db)
|
||||
@@ -4600,8 +4541,7 @@ void Interpreter::Commit() {
|
||||
auto const main_commit = [&](replication::RoleMainData &mainData) {
|
||||
// Only enterprise can do system replication
|
||||
#ifdef MG_ENTERPRISE
|
||||
using enum memgraph::flags::Experiments;
|
||||
if (flags::AreExperimentsEnabled(SYSTEM_REPLICATION) && license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
if (license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return system_transaction_->Commit(memgraph::system::DoReplication{mainData});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -105,14 +105,10 @@ class CoordinatorQueryHandler {
|
||||
};
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void RegisterReplicationInstance(std::string const &coordinator_socket_address,
|
||||
std::string const &replication_socket_address,
|
||||
std::chrono::seconds const &instance_health_check_frequency,
|
||||
std::chrono::seconds const &instance_down_timeout,
|
||||
std::string const &instance_name, CoordinatorQuery::SyncMode sync_mode) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void UnregisterInstance(std::string const &instance_name) = 0;
|
||||
virtual void RegisterReplicationInstance(const std::string &coordinator_socket_address,
|
||||
const std::string &replication_socket_address,
|
||||
const std::chrono::seconds instance_check_frequency,
|
||||
const std::string &instance_name, CoordinatorQuery::SyncMode sync_mode) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void SetReplicationInstanceToMain(const std::string &instance_name) = 0;
|
||||
|
||||
@@ -71,8 +71,6 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
return "RegisterCoordinatorServer"sv;
|
||||
case NotificationCode::ADD_COORDINATOR_INSTANCE:
|
||||
return "AddCoordinatorInstance"sv;
|
||||
case NotificationCode::UNREGISTER_INSTANCE:
|
||||
return "UnregisterInstance"sv;
|
||||
#endif
|
||||
case NotificationCode::REPLICA_PORT_WARNING:
|
||||
return "ReplicaPortWarning"sv;
|
||||
|
||||
@@ -43,9 +43,8 @@ enum class NotificationCode : uint8_t {
|
||||
REPLICA_PORT_WARNING,
|
||||
REGISTER_REPLICA,
|
||||
#ifdef MG_ENTERPRISE
|
||||
REGISTER_COORDINATOR_SERVER, // TODO: (andi) What is this?
|
||||
REGISTER_COORDINATOR_SERVER,
|
||||
ADD_COORDINATOR_INSTANCE,
|
||||
UNREGISTER_INSTANCE,
|
||||
#endif
|
||||
SET_REPLICA,
|
||||
START_STREAM,
|
||||
|
||||
@@ -232,6 +232,7 @@ class RuleBasedPlanner {
|
||||
} else if (auto *load_csv = utils::Downcast<query::LoadCsv>(clause)) {
|
||||
const auto &row_sym = context.symbol_table->at(*load_csv->row_var_);
|
||||
context.bound_symbols.insert(row_sym);
|
||||
|
||||
input_op = std::make_unique<plan::LoadCsv>(std::move(input_op), load_csv->file_, load_csv->with_header_,
|
||||
load_csv->ignore_bad_, load_csv->delimiter_, load_csv->quote_,
|
||||
load_csv->nullif_, row_sym);
|
||||
|
||||
@@ -39,8 +39,7 @@ enum class RegisterReplicaError : uint8_t { NAME_EXISTS, ENDPOINT_EXISTS, COULD_
|
||||
|
||||
struct RoleMainData {
|
||||
RoleMainData() = default;
|
||||
explicit RoleMainData(ReplicationEpoch e, bool writing_enabled, std::optional<utils::UUID> uuid = std::nullopt)
|
||||
: epoch_(std::move(e)), writing_enabled_(writing_enabled) {
|
||||
explicit RoleMainData(ReplicationEpoch e, std::optional<utils::UUID> uuid = std::nullopt) : epoch_(std::move(e)) {
|
||||
if (uuid) {
|
||||
uuid_ = *uuid;
|
||||
}
|
||||
@@ -55,7 +54,6 @@ struct RoleMainData {
|
||||
ReplicationEpoch epoch_;
|
||||
std::list<ReplicationClient> registered_replicas_{}; // TODO: data race issues
|
||||
utils::UUID uuid_;
|
||||
bool writing_enabled_{false};
|
||||
};
|
||||
|
||||
struct RoleReplicaData {
|
||||
@@ -92,21 +90,6 @@ struct ReplicationState {
|
||||
bool IsMain() const { return GetRole() == replication_coordination_glue::ReplicationRole::MAIN; }
|
||||
bool IsReplica() const { return GetRole() == replication_coordination_glue::ReplicationRole::REPLICA; }
|
||||
|
||||
auto IsMainWriteable() const -> bool {
|
||||
if (auto const *main = std::get_if<RoleMainData>(&replication_data_)) {
|
||||
return main->writing_enabled_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto EnableWritingOnMain() -> bool {
|
||||
if (auto *main = std::get_if<RoleMainData>(&replication_data_)) {
|
||||
main->writing_enabled_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasDurability() const { return nullptr != durability_; }
|
||||
|
||||
bool TryPersistRoleMain(std::string new_epoch, utils::UUID main_uuid);
|
||||
|
||||
@@ -57,16 +57,9 @@ ReplicationState::ReplicationState(std::optional<std::filesystem::path> durabili
|
||||
auto replication_data = std::move(fetched_replication_data).GetValue();
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (FLAGS_coordinator_server_port && std::holds_alternative<RoleReplicaData>(replication_data)) {
|
||||
spdlog::trace("Restarted replication uuid for replica");
|
||||
std::get<RoleReplicaData>(replication_data).uuid_.reset();
|
||||
}
|
||||
#endif
|
||||
if (std::holds_alternative<RoleReplicaData>(replication_data)) {
|
||||
spdlog::trace("Recovered main's uuid for replica {}",
|
||||
std::string(std::get<RoleReplicaData>(replication_data).uuid_.value()));
|
||||
} else {
|
||||
spdlog::trace("Recovered uuid for main {}", std::string(std::get<RoleMainData>(replication_data).uuid_));
|
||||
}
|
||||
replication_data_ = std::move(replication_data);
|
||||
}
|
||||
|
||||
@@ -144,8 +137,8 @@ auto ReplicationState::FetchReplicationData() -> FetchReplicationResult_t {
|
||||
return std::visit(
|
||||
utils::Overloaded{
|
||||
[&](durability::MainRole &&r) -> FetchReplicationResult_t {
|
||||
auto res = RoleMainData{std::move(r.epoch), false,
|
||||
r.main_uuid.has_value() ? r.main_uuid.value() : utils::UUID{}};
|
||||
auto res =
|
||||
RoleMainData{std::move(r.epoch), r.main_uuid.has_value() ? r.main_uuid.value() : utils::UUID{}};
|
||||
auto b = durability_->begin(durability::kReplicationReplicaPrefix);
|
||||
auto e = durability_->end(durability::kReplicationReplicaPrefix);
|
||||
for (; b != e; ++b) {
|
||||
@@ -253,7 +246,7 @@ bool ReplicationState::SetReplicationRoleMain(const utils::UUID &main_uuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
replication_data_ = RoleMainData{ReplicationEpoch{new_epoch}, true, main_uuid};
|
||||
replication_data_ = RoleMainData{ReplicationEpoch{new_epoch}, main_uuid};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "flags/experimental.hpp"
|
||||
#include "replication/include/replication/state.hpp"
|
||||
#include "replication_handler/system_rpc.hpp"
|
||||
#include "utils/result.hpp"
|
||||
@@ -23,8 +22,8 @@ inline std::optional<query::RegisterReplicaError> HandleRegisterReplicaStatus(
|
||||
utils::BasicResult<replication::RegisterReplicaError, replication::ReplicationClient *> &instance_client);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
void StartReplicaClient(replication::ReplicationClient &client, system::System &system, dbms::DbmsHandler &dbms_handler,
|
||||
utils::UUID main_uuid, auth::SynchedAuth &auth);
|
||||
void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandler &dbms_handler, utils::UUID main_uuid,
|
||||
system::System *system, auth::SynchedAuth &auth);
|
||||
#else
|
||||
void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandler &dbms_handler, utils::UUID main_uuid);
|
||||
#endif
|
||||
@@ -34,8 +33,8 @@ void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandle
|
||||
// When being called by interpreter no need to gain lock, it should already be under a system transaction
|
||||
// But concurrently the FrequentCheck is running and will need to lock before reading last_committed_system_timestamp_
|
||||
template <bool REQUIRE_LOCK = false>
|
||||
void SystemRestore(replication::ReplicationClient &client, system::System &system, dbms::DbmsHandler &dbms_handler,
|
||||
const utils::UUID &main_uuid, auth::SynchedAuth &auth) {
|
||||
void SystemRestore(replication::ReplicationClient &client, dbms::DbmsHandler &dbms_handler,
|
||||
const utils::UUID &main_uuid, system::System *system, auth::SynchedAuth &auth) {
|
||||
// Check if system is up to date
|
||||
if (client.state_.WithLock(
|
||||
[](auto &state) { return state == memgraph::replication::ReplicationClient::State::READY; }))
|
||||
@@ -43,10 +42,6 @@ void SystemRestore(replication::ReplicationClient &client, system::System &syste
|
||||
|
||||
// Try to recover...
|
||||
{
|
||||
using enum memgraph::flags::Experiments;
|
||||
bool full_system_replication =
|
||||
flags::AreExperimentsEnabled(SYSTEM_REPLICATION) && license::global_license_checker.IsEnterpriseValidFast();
|
||||
// We still need to system replicate
|
||||
struct DbInfo {
|
||||
std::vector<storage::SalientConfig> configs;
|
||||
uint64_t last_committed_timestamp;
|
||||
@@ -54,25 +49,25 @@ void SystemRestore(replication::ReplicationClient &client, system::System &syste
|
||||
DbInfo db_info = std::invoke([&] {
|
||||
auto guard = std::invoke([&]() -> std::optional<memgraph::system::TransactionGuard> {
|
||||
if constexpr (REQUIRE_LOCK) {
|
||||
return system.GenTransactionGuard();
|
||||
return system->GenTransactionGuard();
|
||||
}
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
if (full_system_replication) {
|
||||
if (license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
auto configs = std::vector<storage::SalientConfig>{};
|
||||
dbms_handler.ForEach([&configs](dbms::DatabaseAccess acc) { configs.emplace_back(acc->config().salient); });
|
||||
// TODO: This is `SystemRestore` maybe DbInfo is incorrect as it will need Auth also
|
||||
return DbInfo{configs, system.LastCommittedSystemTimestamp()};
|
||||
return DbInfo{configs, system->LastCommittedSystemTimestamp()};
|
||||
}
|
||||
|
||||
// No license -> send only default config
|
||||
return DbInfo{{dbms_handler.Get()->config().salient}, system.LastCommittedSystemTimestamp()};
|
||||
return DbInfo{{dbms_handler.Get()->config().salient}, system->LastCommittedSystemTimestamp()};
|
||||
});
|
||||
try {
|
||||
auto stream = std::invoke([&]() {
|
||||
// Handle only default database is no license
|
||||
if (!full_system_replication) {
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return client.rpc_client_.Stream<replication::SystemRecoveryRpc>(
|
||||
main_uuid, db_info.last_committed_timestamp, std::move(db_info.configs), auth::Auth::Config{},
|
||||
std::vector<auth::User>{}, std::vector<auth::Role>{});
|
||||
@@ -103,7 +98,7 @@ void SystemRestore(replication::ReplicationClient &client, system::System &syste
|
||||
struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
#ifdef MG_ENTERPRISE
|
||||
explicit ReplicationHandler(memgraph::replication::ReplicationState &repl_state,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::system::System &system,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::system::System *system,
|
||||
memgraph::auth::SynchedAuth &auth);
|
||||
#else
|
||||
explicit ReplicationHandler(memgraph::replication::ReplicationState &repl_state,
|
||||
@@ -138,46 +133,43 @@ struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
auto GetReplState() -> memgraph::replication::ReplicationState &;
|
||||
|
||||
private:
|
||||
template <bool AllowReplicaToDivergeFromMain>
|
||||
auto RegisterReplica_(const memgraph::replication::ReplicationClientConfig &config, bool send_swap_uuid)
|
||||
-> memgraph::utils::BasicResult<memgraph::query::RegisterReplicaError> {
|
||||
template <bool AllowReplicaToBeUnreachable>
|
||||
auto RegisterReplica_(const replication::ReplicationClientConfig &config, bool send_swap_uuid)
|
||||
-> utils::BasicResult<memgraph::query::RegisterReplicaError> {
|
||||
MG_ASSERT(repl_state_.IsMain(), "Only main instance can register a replica!");
|
||||
|
||||
auto maybe_client = repl_state_.RegisterReplica(config);
|
||||
if (maybe_client.HasError()) {
|
||||
switch (maybe_client.GetError()) {
|
||||
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
|
||||
case replication::RegisterReplicaError::NOT_MAIN:
|
||||
MG_ASSERT(false, "Only main instance can register a replica!");
|
||||
return {};
|
||||
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
|
||||
return memgraph::query::RegisterReplicaError::NAME_EXISTS;
|
||||
case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
return memgraph::query::RegisterReplicaError::ENDPOINT_EXISTS;
|
||||
case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return memgraph::query::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case memgraph::replication::RegisterReplicaError::SUCCESS:
|
||||
case replication::RegisterReplicaError::NAME_EXISTS:
|
||||
return query::RegisterReplicaError::NAME_EXISTS;
|
||||
case replication::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
return query::RegisterReplicaError::ENDPOINT_EXISTS;
|
||||
case replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return query::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
using enum memgraph::flags::Experiments;
|
||||
bool system_replication_enabled = flags::AreExperimentsEnabled(SYSTEM_REPLICATION);
|
||||
if (!system_replication_enabled && dbms_handler_.Count() > 1) {
|
||||
if (!dbms::allow_mt_repl && dbms_handler_.All().size() > 1) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
const auto main_uuid =
|
||||
std::get<memgraph::replication::RoleMainData>(dbms_handler_.ReplicationState().ReplicationData()).uuid_;
|
||||
|
||||
if (send_swap_uuid) {
|
||||
if (!memgraph::replication_coordination_glue::SendSwapMainUUIDRpc(maybe_client.GetValue()->rpc_client_,
|
||||
main_uuid)) {
|
||||
return memgraph::query::RegisterReplicaError::ERROR_ACCEPTING_MAIN;
|
||||
}
|
||||
auto const main_uuid =
|
||||
std::get<replication::RoleMainData>(dbms_handler_.ReplicationState().ReplicationData()).uuid_;
|
||||
|
||||
if (send_swap_uuid &&
|
||||
!replication_coordination_glue::SendSwapMainUUIDRpc(maybe_client.GetValue()->rpc_client_, main_uuid)) {
|
||||
return query::RegisterReplicaError::ERROR_ACCEPTING_MAIN;
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Update system before enabling individual storage <-> replica clients
|
||||
SystemRestore(*maybe_client.GetValue(), system_, dbms_handler_, main_uuid, auth_);
|
||||
SystemRestore(*maybe_client.GetValue(), dbms_handler_, main_uuid, system_, auth_);
|
||||
#endif
|
||||
|
||||
const auto dbms_error = HandleRegisterReplicaStatus(maybe_client);
|
||||
@@ -190,7 +182,7 @@ struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
// Add database specific clients (NOTE Currently all databases are connected to each replica)
|
||||
dbms_handler_.ForEach([&](dbms::DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
if (!system_replication_enabled && storage->name() != dbms::kDefaultDB) {
|
||||
if (!dbms::allow_mt_repl && storage->name() != dbms::kDefaultDB) {
|
||||
return;
|
||||
}
|
||||
// TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes
|
||||
@@ -201,13 +193,12 @@ struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
main_uuid](auto &storage_clients) mutable { // NOLINT
|
||||
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()]() {
|
||||
if (state == storage::replication::ReplicaState::DIVERGED_FROM_MAIN) {
|
||||
return AllowReplicaToDivergeFromMain;
|
||||
}
|
||||
return state != storage::replication::ReplicaState::MAYBE_BEHIND;
|
||||
});
|
||||
|
||||
// After start the storage <-> replica state shouldn't be MAYBE_BEHIND.
|
||||
// When part of coordinator cluster we allow replica to be UNREACHABLE.
|
||||
auto state = client->State();
|
||||
bool const success =
|
||||
(state != storage::replication::ReplicaState::MAYBE_BEHIND) ||
|
||||
(state == storage::replication::ReplicaState::UNREACHABLE && AllowReplicaToBeUnreachable);
|
||||
if (success) {
|
||||
storage_clients.push_back(std::move(client));
|
||||
}
|
||||
@@ -215,7 +206,6 @@ struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE Currently if any databases fails, we revert back
|
||||
if (!all_clients_good) {
|
||||
spdlog::error("Failed to register all databases on the REPLICA \"{}\"", config.name);
|
||||
UnregisterReplica(config.name);
|
||||
@@ -224,7 +214,7 @@ struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
|
||||
// No client error, start instance level client
|
||||
#ifdef MG_ENTERPRISE
|
||||
StartReplicaClient(*instance_client_ptr, system_, dbms_handler_, main_uuid, auth_);
|
||||
StartReplicaClient(*instance_client_ptr, dbms_handler_, main_uuid, system_, auth_);
|
||||
#else
|
||||
StartReplicaClient(*instance_client_ptr, dbms_handler_, main_uuid);
|
||||
#endif
|
||||
@@ -235,7 +225,7 @@ struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
memgraph::dbms::DbmsHandler &dbms_handler_;
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::system::System &system_;
|
||||
memgraph::system::System *system_;
|
||||
memgraph::auth::SynchedAuth &auth_;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -27,16 +27,11 @@ inline void LogWrongMain(const std::optional<utils::UUID> ¤t_main_uuid, co
|
||||
#ifdef MG_ENTERPRISE
|
||||
void SystemHeartbeatHandler(uint64_t ts, const std::optional<utils::UUID> ¤t_main_uuid, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
|
||||
void SystemRecoveryHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access,
|
||||
std::optional<utils::UUID> ¤t_main_uuid, dbms::DbmsHandler &dbms_handler,
|
||||
auth::SynchedAuth &auth, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
|
||||
void Register(replication::RoleReplicaData const &data, system::System &system, dbms::DbmsHandler &dbms_handler,
|
||||
auth::SynchedAuth &auth);
|
||||
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, replication::RoleReplicaData &data, auth::SynchedAuth &auth,
|
||||
system::System &system);
|
||||
void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler, auth::SynchedAuth &auth);
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, replication::RoleReplicaData &data, auth::SynchedAuth &auth);
|
||||
#else
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, replication::RoleReplicaData &data);
|
||||
#endif
|
||||
|
||||
@@ -17,25 +17,25 @@ namespace memgraph::replication {
|
||||
|
||||
namespace {
|
||||
#ifdef MG_ENTERPRISE
|
||||
void RecoverReplication(memgraph::replication::ReplicationState &repl_state, memgraph::system::System &system,
|
||||
void RecoverReplication(memgraph::replication::ReplicationState &repl_state, memgraph::system::System *system,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::auth::SynchedAuth &auth) {
|
||||
/*
|
||||
* REPLICATION RECOVERY AND STARTUP
|
||||
*/
|
||||
|
||||
// Startup replication state (if recovered at startup)
|
||||
auto replica = [&dbms_handler, &auth, &system](memgraph::replication::RoleReplicaData &data) {
|
||||
return memgraph::replication::StartRpcServer(dbms_handler, data, auth, system);
|
||||
auto replica = [&dbms_handler, &auth](memgraph::replication::RoleReplicaData &data) {
|
||||
return StartRpcServer(dbms_handler, data, auth);
|
||||
};
|
||||
|
||||
// Replication recovery and frequent check start
|
||||
auto main = [&system, &dbms_handler, &auth](memgraph::replication::RoleMainData &mainData) {
|
||||
auto main = [system, &dbms_handler, &auth](memgraph::replication::RoleMainData &mainData) {
|
||||
for (auto &client : mainData.registered_replicas_) {
|
||||
if (client.try_set_uuid &&
|
||||
replication_coordination_glue::SendSwapMainUUIDRpc(client.rpc_client_, mainData.uuid_)) {
|
||||
client.try_set_uuid = false;
|
||||
}
|
||||
SystemRestore(client, system, dbms_handler, mainData.uuid_, auth);
|
||||
SystemRestore(client, dbms_handler, mainData.uuid_, system, auth);
|
||||
}
|
||||
// DBMS here
|
||||
dbms_handler.ForEach([&mainData](memgraph::dbms::DatabaseAccess db_acc) {
|
||||
@@ -43,7 +43,7 @@ void RecoverReplication(memgraph::replication::ReplicationState &repl_state, mem
|
||||
});
|
||||
|
||||
for (auto &client : mainData.registered_replicas_) {
|
||||
StartReplicaClient(client, system, dbms_handler, mainData.uuid_, auth);
|
||||
StartReplicaClient(client, dbms_handler, mainData.uuid_, system, auth);
|
||||
}
|
||||
|
||||
// Warning
|
||||
@@ -120,8 +120,8 @@ inline std::optional<query::RegisterReplicaError> HandleRegisterReplicaStatus(
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
void StartReplicaClient(replication::ReplicationClient &client, system::System &system, dbms::DbmsHandler &dbms_handler,
|
||||
utils::UUID main_uuid, auth::SynchedAuth &auth) {
|
||||
void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandler &dbms_handler, utils::UUID main_uuid,
|
||||
system::System *system, auth::SynchedAuth &auth) {
|
||||
#else
|
||||
void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandler &dbms_handler,
|
||||
utils::UUID main_uuid) {
|
||||
@@ -129,8 +129,12 @@ void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandle
|
||||
// No client error, start instance level client
|
||||
auto const &endpoint = client.rpc_client_.Endpoint();
|
||||
spdlog::trace("Replication client started at: {}:{}", endpoint.address, endpoint.port);
|
||||
client.StartFrequentCheck([&, license = license::global_license_checker.IsEnterpriseValidFast(), main_uuid](
|
||||
bool reconnect, replication::ReplicationClient &client) mutable {
|
||||
client.StartFrequentCheck([&,
|
||||
#ifdef MG_ENTERPRISE
|
||||
system = system,
|
||||
#endif
|
||||
license = license::global_license_checker.IsEnterpriseValidFast(),
|
||||
main_uuid](bool reconnect, replication::ReplicationClient &client) mutable {
|
||||
if (client.try_set_uuid &&
|
||||
memgraph::replication_coordination_glue::SendSwapMainUUIDRpc(client.rpc_client_, main_uuid)) {
|
||||
client.try_set_uuid = false;
|
||||
@@ -147,7 +151,7 @@ void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandle
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
SystemRestore<true>(client, system, dbms_handler, main_uuid, auth);
|
||||
SystemRestore<true>(client, dbms_handler, main_uuid, system, auth);
|
||||
#endif
|
||||
// Check if any database has been left behind
|
||||
dbms_handler.ForEach([&name = client.name_, reconnect](dbms::DatabaseAccess db_acc) {
|
||||
@@ -164,7 +168,7 @@ void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandle
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
ReplicationHandler::ReplicationHandler(memgraph::replication::ReplicationState &repl_state,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::system::System &system,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::system::System *system,
|
||||
memgraph::auth::SynchedAuth &auth)
|
||||
: repl_state_{repl_state}, dbms_handler_{dbms_handler}, system_{system}, auth_{auth} {
|
||||
RecoverReplication(repl_state_, system_, dbms_handler_, auth_);
|
||||
@@ -194,7 +198,6 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::
|
||||
const std::optional<utils::UUID> &main_uuid) {
|
||||
// We don't want to restart the server if we're already a REPLICA
|
||||
if (repl_state_.IsReplica()) {
|
||||
spdlog::trace("Instance has already has replica role.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -212,19 +215,18 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::
|
||||
repl_state_.SetReplicationRoleReplica(config, main_uuid);
|
||||
|
||||
// Start
|
||||
const auto success =
|
||||
std::visit(memgraph::utils::Overloaded{[](memgraph::replication::RoleMainData &) {
|
||||
// ASSERT
|
||||
return false;
|
||||
},
|
||||
[this](memgraph::replication::RoleReplicaData &data) {
|
||||
const auto success = std::visit(memgraph::utils::Overloaded{[](memgraph::replication::RoleMainData &) {
|
||||
// ASSERT
|
||||
return false;
|
||||
},
|
||||
[this](memgraph::replication::RoleReplicaData &data) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
return StartRpcServer(dbms_handler_, data, auth_, system_);
|
||||
return StartRpcServer(dbms_handler_, data, auth_);
|
||||
#else
|
||||
return StartRpcServer(dbms_handler_, data);
|
||||
return StartRpcServer(dbms_handler_, data);
|
||||
#endif
|
||||
}},
|
||||
repl_state_.ReplicationData());
|
||||
}},
|
||||
repl_state_.ReplicationData());
|
||||
// TODO Handle error (restore to main?)
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "auth/replication_handlers.hpp"
|
||||
#include "dbms/replication_handlers.hpp"
|
||||
#include "flags/experimental.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "replication_handler/system_rpc.hpp"
|
||||
|
||||
@@ -57,24 +56,10 @@ void SystemRecoveryHandler(memgraph::system::ReplicaHandlerAccessToState &system
|
||||
memgraph::replication::SystemRecoveryReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using enum memgraph::flags::Experiments;
|
||||
auto experimental_system_replication = flags::AreExperimentsEnabled(SYSTEM_REPLICATION);
|
||||
|
||||
// validate
|
||||
if (!current_main_uuid.has_value() || req.main_uuid != current_main_uuid) [[unlikely]] {
|
||||
LogWrongMain(current_main_uuid, req.main_uuid, SystemRecoveryReq::kType.name);
|
||||
return;
|
||||
}
|
||||
if (!experimental_system_replication) {
|
||||
if (req.database_configs.size() != 1 && req.database_configs[0].name != dbms::kDefaultDB) {
|
||||
// a partial system recovery should be only be updating the default database uuid
|
||||
return; // Failure sent on exit
|
||||
}
|
||||
if (!req.users.empty() || !req.roles.empty()) {
|
||||
// a partial system recovery should not be updating any users or roles
|
||||
return; // Failure sent on exit
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* DBMS
|
||||
@@ -84,9 +69,7 @@ void SystemRecoveryHandler(memgraph::system::ReplicaHandlerAccessToState &system
|
||||
/*
|
||||
* AUTH
|
||||
*/
|
||||
if (experimental_system_replication) {
|
||||
if (!auth::SystemRecoveryHandler(auth, req.auth_config, req.users, req.roles)) return; // Failure sent on exit
|
||||
}
|
||||
if (!auth::SystemRecoveryHandler(auth, req.auth_config, req.users, req.roles)) return; // Failure sent on exit
|
||||
|
||||
/*
|
||||
* SUCCESSFUL RECOVERY
|
||||
@@ -96,44 +79,35 @@ void SystemRecoveryHandler(memgraph::system::ReplicaHandlerAccessToState &system
|
||||
res = SystemRecoveryRes(SystemRecoveryRes::Result::SUCCESS);
|
||||
}
|
||||
|
||||
void Register(replication::RoleReplicaData const &data, system::System &system, dbms::DbmsHandler &dbms_handler,
|
||||
auth::SynchedAuth &auth) {
|
||||
void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler, auth::SynchedAuth &auth) {
|
||||
// NOTE: Register even without license as the user could add a license at run-time
|
||||
// TODO: fix Register when system is removed from DbmsHandler
|
||||
|
||||
auto system_state_access = system.CreateSystemStateAccess();
|
||||
|
||||
using enum memgraph::flags::Experiments;
|
||||
auto experimental_system_replication = flags::AreExperimentsEnabled(SYSTEM_REPLICATION);
|
||||
auto system_state_access = dbms_handler.system_->CreateSystemStateAccess();
|
||||
|
||||
// System
|
||||
if (experimental_system_replication) {
|
||||
data.server->rpc_server_.Register<replication::SystemHeartbeatRpc>(
|
||||
[&data, system_state_access](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SystemHeartbeatRpc");
|
||||
SystemHeartbeatHandler(system_state_access.LastCommitedTS(), data.uuid_, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
// Needed even with experimental_system_replication=false becasue
|
||||
// need to tell REPLICA the uuid to use for "memgraph" default database
|
||||
// TODO: remove, as this is not used
|
||||
data.server->rpc_server_.Register<replication::SystemHeartbeatRpc>(
|
||||
[&data, system_state_access](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SystemHeartbeatRpc");
|
||||
SystemHeartbeatHandler(system_state_access.LastCommitedTS(), data.uuid_, req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<replication::SystemRecoveryRpc>(
|
||||
[&data, system_state_access, &dbms_handler, &auth](auto *req_reader, auto *res_builder) mutable {
|
||||
spdlog::debug("Received SystemRecoveryRpc");
|
||||
SystemRecoveryHandler(system_state_access, data.uuid_, dbms_handler, auth, req_reader, res_builder);
|
||||
});
|
||||
|
||||
if (experimental_system_replication) {
|
||||
// DBMS
|
||||
dbms::Register(data, system_state_access, dbms_handler);
|
||||
// DBMS
|
||||
dbms::Register(data, system_state_access, dbms_handler);
|
||||
|
||||
// Auth
|
||||
auth::Register(data, system_state_access, auth);
|
||||
}
|
||||
// Auth
|
||||
auth::Register(data, system_state_access, auth);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, replication::RoleReplicaData &data, auth::SynchedAuth &auth,
|
||||
system::System &system) {
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, replication::RoleReplicaData &data, auth::SynchedAuth &auth) {
|
||||
#else
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, replication::RoleReplicaData &data) {
|
||||
#endif
|
||||
@@ -141,7 +115,7 @@ bool StartRpcServer(dbms::DbmsHandler &dbms_handler, replication::RoleReplicaDat
|
||||
dbms::InMemoryReplicationHandlers::Register(&dbms_handler, data);
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Register system handlers
|
||||
Register(data, system, dbms_handler, auth);
|
||||
Register(data, dbms_handler, auth);
|
||||
#endif
|
||||
// Start server
|
||||
if (!data.server->Start()) {
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
|
||||
namespace memgraph::storage::replication {
|
||||
|
||||
enum class ReplicaState : std::uint8_t { READY, REPLICATING, RECOVERY, MAYBE_BEHIND, DIVERGED_FROM_MAIN };
|
||||
enum class ReplicaState : std::uint8_t { READY, REPLICATING, RECOVERY, MAYBE_BEHIND, UNREACHABLE };
|
||||
|
||||
} // namespace memgraph::storage::replication
|
||||
|
||||
@@ -46,6 +46,9 @@ void ReplicationStorageClient::UpdateReplicaState(Storage *storage, DatabaseAcce
|
||||
std::string{storage->uuid()});
|
||||
state = memgraph::replication::ReplicationClient::State::BEHIND;
|
||||
});
|
||||
|
||||
replica_state_.WithLock([](auto &state) { state = replication::ReplicaState::UNREACHABLE; });
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -69,7 +72,7 @@ void ReplicationStorageClient::UpdateReplicaState(Storage *storage, DatabaseAcce
|
||||
"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; });
|
||||
// State not updated, hence in MAYBE_BEHIND state
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,6 +152,9 @@ void ReplicationStorageClient::StartTransactionReplication(const uint64_t curren
|
||||
auto locked_state = replica_state_.Lock();
|
||||
switch (*locked_state) {
|
||||
using enum replication::ReplicaState;
|
||||
case UNREACHABLE:
|
||||
spdlog::debug("Replica {} is unreachable", client_.name_);
|
||||
return;
|
||||
case RECOVERY:
|
||||
spdlog::debug("Replica {} is behind MAIN instance", client_.name_);
|
||||
return;
|
||||
@@ -171,10 +177,6 @@ void ReplicationStorageClient::StartTransactionReplication(const uint64_t curren
|
||||
utils::MessageWithLink("Couldn't replicate data to {}.", client_.name_, "https://memgr.ph/replication"));
|
||||
TryCheckReplicaStateAsync(storage, std::move(db_acc));
|
||||
return;
|
||||
case DIVERGED_FROM_MAIN:
|
||||
spdlog::error(utils::MessageWithLink("Couldn't replicate data to {} since replica has diverged from main.",
|
||||
client_.name_, "https://memgr.ph/replication"));
|
||||
return;
|
||||
case READY:
|
||||
MG_ASSERT(!replica_stream_);
|
||||
try {
|
||||
|
||||
@@ -26,14 +26,12 @@ struct ISystemAction {
|
||||
/// Durability step which is defered until commit time
|
||||
virtual void DoDurability() = 0;
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/// Prepare the RPC payload that will be sent to all replicas clients
|
||||
virtual bool DoReplication(memgraph::replication::ReplicationClient &client, const utils::UUID &main_uuid,
|
||||
memgraph::replication::ReplicationEpoch const &epoch,
|
||||
Transaction const &system_tx) const = 0;
|
||||
|
||||
virtual void PostReplication(memgraph::replication::RoleMainData &main_data) const = 0;
|
||||
#endif
|
||||
|
||||
virtual ~ISystemAction() = default;
|
||||
};
|
||||
|
||||
@@ -57,13 +57,11 @@ struct Transaction {
|
||||
/// durability
|
||||
action->DoDurability();
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/// replication
|
||||
/// replication prep
|
||||
auto action_sync_status = handler.ApplyAction(*action, *this);
|
||||
if (action_sync_status != AllSyncReplicaStatus::AllCommitsConfirmed) {
|
||||
sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
|
||||
}
|
||||
#endif
|
||||
|
||||
actions_.pop_front();
|
||||
}
|
||||
@@ -95,7 +93,6 @@ struct Transaction {
|
||||
std::list<std::unique_ptr<ISystemAction>> actions_;
|
||||
};
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
struct DoReplication {
|
||||
explicit DoReplication(replication::RoleMainData &main_data) : main_data_{main_data} {}
|
||||
auto ApplyAction(ISystemAction const &action, Transaction const &system_tx) -> AllSyncReplicaStatus {
|
||||
@@ -116,7 +113,6 @@ struct DoReplication {
|
||||
replication::RoleMainData &main_data_;
|
||||
};
|
||||
static_assert(ReplicationPolicy<DoReplication>);
|
||||
#endif
|
||||
|
||||
struct DoNothing {
|
||||
auto ApplyAction(ISystemAction const & /*action*/, Transaction const & /*system_tx*/) -> AllSyncReplicaStatus {
|
||||
|
||||
@@ -107,10 +107,6 @@ enum class TypeId : uint64_t {
|
||||
COORD_SET_REPL_MAIN_RES,
|
||||
COORD_SWAP_UUID_REQ,
|
||||
COORD_SWAP_UUID_RES,
|
||||
COORD_UNREGISTER_REPLICA_REQ,
|
||||
COORD_UNREGISTER_REPLICA_RES,
|
||||
COORD_ENABLE_WRITING_ON_MAIN_REQ,
|
||||
COORD_ENABLE_WRITING_ON_MAIN_RES,
|
||||
|
||||
// AST
|
||||
AST_LABELIX = 3000,
|
||||
|
||||
@@ -47,12 +47,9 @@ func check_tx(driver neo4j.Driver) {
|
||||
log.Fatal("An error occurred while creating a session: %s", err)
|
||||
}
|
||||
|
||||
defer session.Close()
|
||||
|
||||
result, err := session.Run("SHOW TRANSACTIONS", nil)
|
||||
if err != nil {
|
||||
log.Fatal("An error occurred while running a query: %s", err)
|
||||
}
|
||||
defer session.Close()
|
||||
check_md(result, err)
|
||||
}
|
||||
|
||||
@@ -86,7 +83,7 @@ func main() {
|
||||
handle_error(err)
|
||||
}
|
||||
tx.Run("MATCH (n) RETURN n LIMIT 1", map[string]interface{}{})
|
||||
check_tx(driver)
|
||||
go check_tx(driver)
|
||||
tx.Commit()
|
||||
|
||||
fmt.Println("All ok!")
|
||||
|
||||
@@ -40,7 +40,7 @@ endfunction()
|
||||
add_subdirectory(fine_grained_access)
|
||||
add_subdirectory(server)
|
||||
add_subdirectory(replication)
|
||||
#add_subdirectory(memory)
|
||||
add_subdirectory(memory)
|
||||
add_subdirectory(triggers)
|
||||
add_subdirectory(isolation_levels)
|
||||
add_subdirectory(streams)
|
||||
@@ -56,6 +56,7 @@ add_subdirectory(python_query_modules_reloading)
|
||||
add_subdirectory(analyze_graph)
|
||||
add_subdirectory(transaction_queue)
|
||||
add_subdirectory(mock_api)
|
||||
add_subdirectory(graphql)
|
||||
add_subdirectory(disk_storage)
|
||||
add_subdirectory(load_csv)
|
||||
add_subdirectory(init_file_flags)
|
||||
@@ -80,7 +81,10 @@ if (MG_EXPERIMENTAL_HIGH_AVAILABILITY)
|
||||
add_subdirectory(high_availability_experimental)
|
||||
endif ()
|
||||
|
||||
add_subdirectory(replication_experimental)
|
||||
|
||||
if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||
add_subdirectory(replication_experimental)
|
||||
endif ()
|
||||
|
||||
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
|
||||
copy_e2e_python_files(x x.sh "")
|
||||
|
||||
@@ -69,8 +69,6 @@ startup_config_dict = {
|
||||
"coordinator_server_port": ("0", "0", "Port on which coordinator servers will be started."),
|
||||
"raft_server_port": ("0", "0", "Port on which raft servers will be started."),
|
||||
"raft_server_id": ("0", "0", "Unique ID of the raft server."),
|
||||
"instance_down_timeout_sec": ("5", "5", "Time duration after which an instance is considered down."),
|
||||
"instance_health_check_frequency_sec": ("1", "1", "The time duration between two health checks/pings."),
|
||||
"data_directory": ("mg_data", "mg_data", "Path to directory in which to save all permanent data."),
|
||||
"data_recovery_on_startup": (
|
||||
"false",
|
||||
@@ -224,9 +222,4 @@ startup_config_dict = {
|
||||
"128",
|
||||
"The threshold for when to cache long delta chains. This is used for heavy read + write workloads where repeated processing of delta chains can become costly.",
|
||||
),
|
||||
"experimental_enabled": (
|
||||
"",
|
||||
"",
|
||||
"Experimental features to be used, comma seperated. Options [system-replication]",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ copy_e2e_python_files(ha_experimental coord_cluster_registration.py)
|
||||
copy_e2e_python_files(ha_experimental distributed_coords.py)
|
||||
copy_e2e_python_files(ha_experimental manual_setting_replicas.py)
|
||||
copy_e2e_python_files(ha_experimental not_replicate_from_old_main.py)
|
||||
copy_e2e_python_files(ha_experimental disable_writing_on_main_after_restart.py)
|
||||
copy_e2e_python_files(ha_experimental common.py)
|
||||
copy_e2e_python_files(ha_experimental workloads.yaml)
|
||||
|
||||
|
||||
@@ -30,14 +30,3 @@ def safe_execute(function, *args):
|
||||
function(*args)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# NOTE: Repeated execution because it can fail if Raft server is not up
|
||||
def add_coordinator(cursor, query):
|
||||
for _ in range(10):
|
||||
try:
|
||||
execute_and_fetch_all(cursor, query)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
@@ -16,7 +16,7 @@ import tempfile
|
||||
|
||||
import interactive_mg_runner
|
||||
import pytest
|
||||
from common import add_coordinator, connect, execute_and_fetch_all, safe_execute
|
||||
from common import connect, execute_and_fetch_all, safe_execute
|
||||
from mg_utils import mg_sleep_and_assert
|
||||
|
||||
interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -104,6 +104,17 @@ MEMGRAPH_INSTANCES_DESCRIPTION = {
|
||||
}
|
||||
|
||||
|
||||
# NOTE: Repeated execution because it can fail if Raft server is not up
|
||||
def add_coordinator(cursor, query):
|
||||
for _ in range(10):
|
||||
try:
|
||||
execute_and_fetch_all(cursor, query)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def test_register_repl_instances_then_coordinators():
|
||||
safe_execute(shutil.rmtree, TEMP_DIR)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
@@ -269,148 +280,5 @@ def test_coordinators_communication_with_restarts():
|
||||
mg_sleep_and_assert(expected_cluster_not_shared, check_coordinator2)
|
||||
|
||||
|
||||
# TODO: (andi) Test when dealing with distributed coordinators that you can register on one coordinator and unregister from any other coordinator
|
||||
@pytest.mark.parametrize(
|
||||
"kill_instance",
|
||||
[True, False],
|
||||
)
|
||||
def test_unregister_replicas(kill_instance):
|
||||
safe_execute(shutil.rmtree, TEMP_DIR)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
coordinator3_cursor = connect(host="localhost", port=7692).cursor()
|
||||
execute_and_fetch_all(
|
||||
coordinator3_cursor, "REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001'"
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
coordinator3_cursor, "REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002'"
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
coordinator3_cursor, "REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003'"
|
||||
)
|
||||
execute_and_fetch_all(coordinator3_cursor, "SET INSTANCE instance_3 TO MAIN")
|
||||
|
||||
def check_coordinator3():
|
||||
return sorted(list(execute_and_fetch_all(coordinator3_cursor, "SHOW INSTANCES")))
|
||||
|
||||
main_cursor = connect(host="localhost", port=7689).cursor()
|
||||
|
||||
def check_main():
|
||||
return sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS")))
|
||||
|
||||
expected_cluster = [
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", True, "replica"),
|
||||
("instance_2", "", "127.0.0.1:10012", True, "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", True, "main"),
|
||||
]
|
||||
|
||||
expected_replicas = [
|
||||
("instance_1", "127.0.0.1:10001", "sync", 0, 0, "ready"),
|
||||
("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_cluster, check_coordinator3)
|
||||
mg_sleep_and_assert(expected_replicas, check_main)
|
||||
|
||||
if kill_instance:
|
||||
interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_1")
|
||||
execute_and_fetch_all(coordinator3_cursor, "UNREGISTER INSTANCE instance_1")
|
||||
|
||||
expected_cluster = [
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_2", "", "127.0.0.1:10012", True, "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", True, "main"),
|
||||
]
|
||||
|
||||
expected_replicas = [
|
||||
("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_cluster, check_coordinator3)
|
||||
mg_sleep_and_assert(expected_replicas, check_main)
|
||||
|
||||
if kill_instance:
|
||||
interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_2")
|
||||
execute_and_fetch_all(coordinator3_cursor, "UNREGISTER INSTANCE instance_2")
|
||||
|
||||
expected_cluster = [
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_3", "", "127.0.0.1:10013", True, "main"),
|
||||
]
|
||||
expected_replicas = []
|
||||
|
||||
mg_sleep_and_assert(expected_cluster, check_coordinator3)
|
||||
mg_sleep_and_assert(expected_replicas, check_main)
|
||||
|
||||
|
||||
def test_unregister_main():
|
||||
safe_execute(shutil.rmtree, TEMP_DIR)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
coordinator3_cursor = connect(host="localhost", port=7692).cursor()
|
||||
execute_and_fetch_all(
|
||||
coordinator3_cursor, "REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001'"
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
coordinator3_cursor, "REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002'"
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
coordinator3_cursor, "REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003'"
|
||||
)
|
||||
execute_and_fetch_all(coordinator3_cursor, "SET INSTANCE instance_3 TO MAIN")
|
||||
|
||||
def check_coordinator3():
|
||||
return sorted(list(execute_and_fetch_all(coordinator3_cursor, "SHOW INSTANCES")))
|
||||
|
||||
expected_cluster = [
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", True, "replica"),
|
||||
("instance_2", "", "127.0.0.1:10012", True, "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", True, "main"),
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_cluster, check_coordinator3)
|
||||
|
||||
try:
|
||||
execute_and_fetch_all(coordinator3_cursor, "UNREGISTER INSTANCE instance_3")
|
||||
except Exception as e:
|
||||
assert (
|
||||
str(e)
|
||||
== "Alive main instance can't be unregistered! Shut it down to trigger failover and then unregister it!"
|
||||
)
|
||||
|
||||
interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
|
||||
expected_cluster = [
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", True, "main"),
|
||||
("instance_2", "", "127.0.0.1:10012", True, "replica"),
|
||||
("instance_3", "", "127.0.0.1:10013", False, "unknown"),
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_cluster, check_coordinator3)
|
||||
|
||||
execute_and_fetch_all(coordinator3_cursor, "UNREGISTER INSTANCE instance_3")
|
||||
|
||||
expected_cluster = [
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_1", "", "127.0.0.1:10011", True, "main"),
|
||||
("instance_2", "", "127.0.0.1:10012", True, "replica"),
|
||||
]
|
||||
|
||||
expected_replicas = [
|
||||
("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
|
||||
]
|
||||
|
||||
main_cursor = connect(host="localhost", port=7687).cursor()
|
||||
|
||||
def check_main():
|
||||
return sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS")))
|
||||
|
||||
mg_sleep_and_assert(expected_cluster, check_coordinator3)
|
||||
mg_sleep_and_assert(expected_replicas, check_main)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
# Copyright 2022 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.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import interactive_mg_runner
|
||||
import pytest
|
||||
from common import add_coordinator, connect, execute_and_fetch_all, safe_execute
|
||||
from mg_utils import mg_sleep_and_assert
|
||||
|
||||
interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
interactive_mg_runner.PROJECT_DIR = os.path.normpath(
|
||||
os.path.join(interactive_mg_runner.SCRIPT_DIR, "..", "..", "..", "..")
|
||||
)
|
||||
interactive_mg_runner.BUILD_DIR = os.path.normpath(os.path.join(interactive_mg_runner.PROJECT_DIR, "build"))
|
||||
interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactive_mg_runner.BUILD_DIR, "memgraph"))
|
||||
|
||||
TEMP_DIR = tempfile.TemporaryDirectory().name
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = {
|
||||
"instance_1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7687",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10011",
|
||||
"--also-log-to-stderr",
|
||||
"--instance-health-check-frequency-sec",
|
||||
"1",
|
||||
"--instance-down-timeout-sec",
|
||||
"5",
|
||||
],
|
||||
"log_file": "instance_1.log",
|
||||
"data_directory": f"{TEMP_DIR}/instance_1",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"instance_2": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10012",
|
||||
"--also-log-to-stderr",
|
||||
"--instance-health-check-frequency-sec",
|
||||
"1",
|
||||
"--instance-down-timeout-sec",
|
||||
"5",
|
||||
],
|
||||
"log_file": "instance_2.log",
|
||||
"data_directory": f"{TEMP_DIR}/instance_2",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"instance_3": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7689",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--coordinator-server-port",
|
||||
"10013",
|
||||
"--also-log-to-stderr",
|
||||
"--instance-health-check-frequency-sec",
|
||||
"5",
|
||||
"--instance-down-timeout-sec",
|
||||
"10",
|
||||
],
|
||||
"log_file": "instance_3.log",
|
||||
"data_directory": f"{TEMP_DIR}/instance_3",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator_1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7690",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=1",
|
||||
"--raft-server-port=10111",
|
||||
],
|
||||
"log_file": "coordinator1.log",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator_2": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7691",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=2",
|
||||
"--raft-server-port=10112",
|
||||
],
|
||||
"log_file": "coordinator2.log",
|
||||
"setup_queries": [],
|
||||
},
|
||||
"coordinator_3": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7692",
|
||||
"--log-level=TRACE",
|
||||
"--raft-server-id=3",
|
||||
"--raft-server-port=10113",
|
||||
"--also-log-to-stderr",
|
||||
],
|
||||
"log_file": "coordinator3.log",
|
||||
"setup_queries": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_writing_disabled_on_main_restart():
|
||||
safe_execute(shutil.rmtree, TEMP_DIR)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
coordinator3_cursor = connect(host="localhost", port=7692).cursor()
|
||||
|
||||
execute_and_fetch_all(
|
||||
coordinator3_cursor, "REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003'"
|
||||
)
|
||||
execute_and_fetch_all(coordinator3_cursor, "SET INSTANCE instance_3 TO MAIN")
|
||||
assert add_coordinator(coordinator3_cursor, "ADD COORDINATOR 1 ON '127.0.0.1:10111'")
|
||||
assert add_coordinator(coordinator3_cursor, "ADD COORDINATOR 2 ON '127.0.0.1:10112'")
|
||||
|
||||
def check_coordinator3():
|
||||
return sorted(list(execute_and_fetch_all(coordinator3_cursor, "SHOW INSTANCES")))
|
||||
|
||||
expected_cluster_coord3 = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", True, "coordinator"),
|
||||
("coordinator_2", "127.0.0.1:10112", "", True, "coordinator"),
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_3", "", "127.0.0.1:10013", True, "main"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_cluster_coord3, check_coordinator3)
|
||||
|
||||
interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
|
||||
expected_cluster_coord3 = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", True, "coordinator"),
|
||||
("coordinator_2", "127.0.0.1:10112", "", True, "coordinator"),
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_3", "", "127.0.0.1:10013", False, "unknown"),
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_cluster_coord3, check_coordinator3)
|
||||
|
||||
interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
|
||||
try:
|
||||
instance3_cursor = connect(host="localhost", port=7689).cursor()
|
||||
execute_and_fetch_all(instance3_cursor, "CREATE (n:Node {name: 'node'})")
|
||||
except Exception as e:
|
||||
assert (
|
||||
str(e)
|
||||
== "Write query forbidden on the main! Coordinator needs to enable writing on main by sending RPC message."
|
||||
)
|
||||
|
||||
expected_cluster_coord3 = [
|
||||
("coordinator_1", "127.0.0.1:10111", "", True, "coordinator"),
|
||||
("coordinator_2", "127.0.0.1:10112", "", True, "coordinator"),
|
||||
("coordinator_3", "127.0.0.1:10113", "", True, "coordinator"),
|
||||
("instance_3", "", "127.0.0.1:10013", True, "main"),
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_cluster_coord3, check_coordinator3)
|
||||
execute_and_fetch_all(instance3_cursor, "CREATE (n:Node {name: 'node'})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
@@ -154,7 +154,7 @@ def test_distributed_automatic_failover():
|
||||
interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
expected_data_on_new_main_old_alive = [
|
||||
("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
|
||||
("instance_3", "127.0.0.1:10003", "sync", 0, 0, "ready"),
|
||||
("instance_3", "127.0.0.1:10003", "sync", 0, 0, "invalid"), # TODO: (andi) Solve it
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_data_on_new_main_old_alive, retrieve_data_show_replicas)
|
||||
|
||||
@@ -133,18 +133,18 @@ def test_replication_works_on_failover():
|
||||
interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
expected_data_on_new_main = [
|
||||
("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
|
||||
("instance_3", "127.0.0.1:10003", "sync", 0, 0, "ready"),
|
||||
("instance_3", "127.0.0.1:10003", "sync", 0, 0, "invalid"),
|
||||
]
|
||||
mg_sleep_and_assert(expected_data_on_new_main, retrieve_data_show_replicas)
|
||||
|
||||
# 5
|
||||
execute_and_fetch_all(new_main_cursor, "CREATE ();")
|
||||
# execute_and_fetch_all(new_main_cursor, "CREATE ();")
|
||||
|
||||
# 6
|
||||
alive_replica_cursror = connect(host="localhost", port=7689).cursor()
|
||||
res = execute_and_fetch_all(alive_replica_cursror, "MATCH (n) RETURN count(n) as count;")[0][0]
|
||||
assert res == 1, "Vertex should be replicated"
|
||||
interactive_mg_runner.stop_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
# alive_replica_cursror = connect(host="localhost", port=7689).cursor()
|
||||
# res = execute_and_fetch_all(alive_replica_cursror, "MATCH (n) RETURN count(n) as count;")[0][0]
|
||||
# assert res == 1, "Vertex should be replicated"
|
||||
# interactive_mg_runner.stop_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
|
||||
def test_show_instances():
|
||||
@@ -242,7 +242,7 @@ def test_simple_automatic_failover():
|
||||
interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3")
|
||||
expected_data_on_new_main_old_alive = [
|
||||
("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"),
|
||||
("instance_3", "127.0.0.1:10003", "sync", 0, 0, "ready"),
|
||||
("instance_3", "127.0.0.1:10003", "sync", 0, 0, "invalid"),
|
||||
]
|
||||
|
||||
mg_sleep_and_assert(expected_data_on_new_main_old_alive, retrieve_data_show_replicas)
|
||||
|
||||
@@ -44,10 +44,6 @@ workloads:
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
args: ["high_availability_experimental/not_replicate_from_old_main.py"]
|
||||
|
||||
- name: "Disable writing on main after restart"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
args: ["high_availability_experimental/disable_writing_on_main_after_restart.py"]
|
||||
|
||||
- name: "Distributed coordinators"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
args: ["high_availability_experimental/distributed_coords.py"]
|
||||
|
||||
@@ -48,6 +48,16 @@ read_query_modules_in_memory_cluster: &read_query_modules_in_memory_cluster
|
||||
setup_queries: *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
read_query_modules_disk_cluster: &read_query_modules_disk_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: *args
|
||||
log_file: "disk-lba-e2e.log"
|
||||
setup_queries:
|
||||
- "STORAGE MODE ON_DISK_TRANSACTIONAL"
|
||||
- *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
update_query_modules_in_memory_cluster: &update_query_modules_in_memory_cluster
|
||||
cluster:
|
||||
main:
|
||||
@@ -56,6 +66,16 @@ update_query_modules_in_memory_cluster: &update_query_modules_in_memory_cluster
|
||||
setup_queries: *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
update_query_modules_disk_cluster: &update_query_modules_disk_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: *args
|
||||
log_file: "disk-lba-e2e.log"
|
||||
setup_queries:
|
||||
- "STORAGE MODE ON_DISK_TRANSACTIONAL"
|
||||
- *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
show_privileges_in_memory_cluster: &show_privileges_in_memory_cluster
|
||||
cluster:
|
||||
main:
|
||||
@@ -64,6 +84,16 @@ show_privileges_in_memory_cluster: &show_privileges_in_memory_cluster
|
||||
setup_queries: *show_privileges_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
show_privileges_disk_cluster: &show_privileges_disk_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: *args
|
||||
log_file: "disk-lba-e2e.log"
|
||||
setup_queries:
|
||||
- "STORAGE MODE ON_DISK_TRANSACTIONAL"
|
||||
- *show_privileges_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
read_permission_in_memory_queries: &read_permission_in_memory_queries
|
||||
cluster:
|
||||
main:
|
||||
@@ -72,6 +102,17 @@ read_permission_in_memory_queries: &read_permission_in_memory_queries
|
||||
setup_queries: *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
read_permission_disk_queries: &read_permission_disk_queries
|
||||
cluster:
|
||||
main:
|
||||
args: *args
|
||||
log_file: "disk-lba-e2e.log"
|
||||
setup_queries:
|
||||
- "STORAGE MODE ON_DISK_TRANSACTIONAL"
|
||||
- *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
|
||||
create_delete_query_modules_in_memory_cluster: &create_delete_query_modules_in_memory_cluster
|
||||
cluster:
|
||||
main:
|
||||
@@ -80,6 +121,16 @@ create_delete_query_modules_in_memory_cluster: &create_delete_query_modules_in_m
|
||||
setup_queries: *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
create_delete_query_modules_disk_cluster: &create_delete_query_modules_disk_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: *args
|
||||
log_file: "disk-lba-e2e.log"
|
||||
setup_queries:
|
||||
- "STORAGE MODE ON_DISK_TRANSACTIONAL"
|
||||
- *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
update_permission_queries_in_memory_cluster: &update_permission_queries_in_memory_cluster
|
||||
cluster:
|
||||
main:
|
||||
@@ -88,6 +139,16 @@ update_permission_queries_in_memory_cluster: &update_permission_queries_in_memor
|
||||
setup_queries: *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
update_permission_queries_disk_cluster: &update_permission_queries_disk_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: *args
|
||||
log_file: "disk-lba-e2e.log"
|
||||
setup_queries:
|
||||
- "STORAGE MODE ON_DISK_TRANSACTIONAL"
|
||||
- *query_modules_setup_queries
|
||||
validation_queries: []
|
||||
|
||||
workloads:
|
||||
- name: "read-query-modules"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
@@ -95,32 +156,68 @@ workloads:
|
||||
args: ["lba_procedures/read_query_modules.py"]
|
||||
<<: *read_query_modules_in_memory_cluster
|
||||
|
||||
- name: "read-query-modules on disk"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/read_query_modules.py"]
|
||||
<<: *read_query_modules_disk_cluster
|
||||
|
||||
- name: "update-query-modules"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/update_query_modules.py"]
|
||||
<<: *update_query_modules_in_memory_cluster
|
||||
|
||||
- name: "update-query-modules on disk"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/update_query_modules.py"]
|
||||
<<: *update_query_modules_disk_cluster
|
||||
|
||||
- name: "create-delete-query-modules"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/create_delete_query_modules.py"]
|
||||
<<: *create_delete_query_modules_in_memory_cluster
|
||||
|
||||
- name: "create-delete-query-modules on disk"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/create_delete_query_modules.py"]
|
||||
<<: *create_delete_query_modules_disk_cluster
|
||||
|
||||
- name: "show-privileges"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/show_privileges.py"]
|
||||
<<: *show_privileges_in_memory_cluster
|
||||
|
||||
- name: "show-privileges on disk"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/show_privileges.py"]
|
||||
<<: *show_privileges_disk_cluster
|
||||
|
||||
- name: "read-permission-queries"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/read_permission_queries.py"]
|
||||
<<: *read_permission_in_memory_queries
|
||||
|
||||
- name: "read-permission-queries on disk"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/read_permission_queries.py"]
|
||||
<<: *read_permission_disk_queries
|
||||
|
||||
- name: "update-permission-queries"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/update_permission_queries.py"]
|
||||
<<: *update_permission_queries_in_memory_cluster
|
||||
|
||||
- name: "update-permission-queries on disk"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/update_permission_queries.py"]
|
||||
<<: *update_permission_queries_disk_cluster
|
||||
|
||||
@@ -16,7 +16,6 @@ from pathlib import Path
|
||||
import pytest
|
||||
from gqlalchemy import Memgraph
|
||||
from mgclient import DatabaseError
|
||||
from neo4j import GraphDatabase
|
||||
|
||||
SIMPLE_CSV_FILE = "simple.csv"
|
||||
|
||||
@@ -53,22 +52,5 @@ def test_given_one_row_in_db_when_load_csv_after_match_then_pass():
|
||||
assert len(list(results)) == 4
|
||||
|
||||
|
||||
def test_load_csv_with_parameters():
|
||||
memgraph = Memgraph("localhost", 7687)
|
||||
URI = "bolt://localhost:7687"
|
||||
AUTH = ("", "")
|
||||
|
||||
with GraphDatabase.driver(URI, auth=AUTH) as client:
|
||||
with client.session(database="memgraph") as session:
|
||||
results = session.run(
|
||||
f"""MATCH (n {{prop: 1}}) LOAD CSV
|
||||
FROM $file WITH HEADER AS row
|
||||
CREATE (:Person {{name: row.name}})
|
||||
RETURN n""",
|
||||
file=get_file_path(SIMPLE_CSV_FILE),
|
||||
)
|
||||
assert len(list(results)) == 4
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
|
||||
@@ -151,10 +151,7 @@ class MemgraphInstanceRunner:
|
||||
|
||||
if not keep_directories:
|
||||
for folder in self.delete_on_stop or {}:
|
||||
try:
|
||||
shutil.rmtree(folder)
|
||||
except Exception as e:
|
||||
pass # couldn't delete folder, skip
|
||||
shutil.rmtree(folder)
|
||||
|
||||
def kill(self, keep_directories=False):
|
||||
if not self.is_running():
|
||||
|
||||
@@ -135,6 +135,11 @@ workloads:
|
||||
proc: "tests/e2e/memory/procedures/"
|
||||
<<: *in_memory_query_limit_cluster
|
||||
|
||||
- name: "Memory control query limit create"
|
||||
binary: "tests/e2e/memory/memgraph__e2e__memory__limit_query_alloc_create"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
<<: *in_memory_query_limit_cluster
|
||||
|
||||
- name: "Memory control query limit create multi thread"
|
||||
binary: "tests/e2e/memory/memgraph__e2e__memory__limit_query_alloc_create_multi_thread"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
|
||||
@@ -26,3 +26,10 @@ workloads:
|
||||
proc: "query_modules/"
|
||||
args: ["query_modules/mgps_test.py"]
|
||||
<<: *in_memory_cluster
|
||||
|
||||
- name: "Schema test"
|
||||
pre_set_workload: "tests/e2e/x.sh"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "query_modules/"
|
||||
args: ["query_modules/schema_test.py"]
|
||||
<<: *in_memory_cluster
|
||||
|
||||
@@ -260,14 +260,7 @@ def test_drop_replicas(connection):
|
||||
mg_sleep_and_assert(expected_data, retrieve_data)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"recover_data_on_startup",
|
||||
[
|
||||
"true",
|
||||
"false",
|
||||
],
|
||||
)
|
||||
def test_basic_recovery(recover_data_on_startup, connection):
|
||||
def test_basic_recovery(connection):
|
||||
# Goal of this test is to check the recovery of main.
|
||||
# 0/ We start all replicas manually: we want to be able to kill them ourselves without relying on external tooling to kill processes.
|
||||
# 1/ We check that all replicas have the correct state: they should all be ready.
|
||||
@@ -279,9 +272,9 @@ def test_basic_recovery(recover_data_on_startup, connection):
|
||||
# 7/ We check that all replicas but one have the expected data.
|
||||
# 8/ We kill another replica.
|
||||
# 9/ We add some data to main.
|
||||
# 10/ We re-add the two replicas dropped/killed and check the data.
|
||||
# 10/ We re-add the two replicas droped/killed and check the data.
|
||||
# 11/ We kill another replica.
|
||||
# 12/ Add some more data to main. It must still occur but exception is expected since one replica is down.
|
||||
# 12/ Add some more data to main. It must still still occured but exception is expected since one replica is down.
|
||||
# 13/ Restart the replica
|
||||
# 14/ Check the states of replicas.
|
||||
# 15/ Add some data again.
|
||||
@@ -291,60 +284,22 @@ def test_basic_recovery(recover_data_on_startup, connection):
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE", "--replication-restore-state-on-startup=true"],
|
||||
"log_file": "replica1.log",
|
||||
# Need to set it up manually
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/replica_1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7689",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE", "--replication-restore-state-on-startup=true"],
|
||||
"log_file": "replica2.log",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10002;"],
|
||||
"data_directory": f"{data_directory.name}/replica_2",
|
||||
},
|
||||
"replica_3": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7690",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
f"{recover_data_on_startup}",
|
||||
],
|
||||
"args": ["--bolt-port", "7690", "--log-level=TRACE", "--replication-restore-state-on-startup=true"],
|
||||
"log_file": "replica3.log",
|
||||
# We restart this replica so we set replication role manually,
|
||||
# On restart we would set replication role again, we want to get it from data
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/replica_3",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10003;"],
|
||||
},
|
||||
"replica_4": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7691",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7691", "--log-level=TRACE", "--replication-restore-state-on-startup=true"],
|
||||
"log_file": "replica4.log",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10004;"],
|
||||
},
|
||||
@@ -358,18 +313,11 @@ def test_basic_recovery(recover_data_on_startup, connection):
|
||||
],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/main",
|
||||
"data_directory": f"{data_directory.name}",
|
||||
},
|
||||
}
|
||||
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
replica_1_cursor = connection(7688, "replica_1").cursor()
|
||||
execute_and_fetch_all(replica_1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
replica_3_cursor = connection(7690, "replica_3").cursor()
|
||||
execute_and_fetch_all(replica_3_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10003;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, otherwise re-starting main would also execute these registration.
|
||||
@@ -537,32 +485,26 @@ def test_basic_recovery(recover_data_on_startup, connection):
|
||||
def test_replication_role_recovery(connection):
|
||||
# Goal of this test is to check the recovery of main and replica role.
|
||||
# 0/ We start all replicas manually: we want to be able to kill them ourselves without relying on external tooling to kill processes.
|
||||
# 1/ We check that all replicas have the correct state: they should all be ready.
|
||||
# 2/ We kill main.
|
||||
# 3/ We re-start main. We check that main indeed has the role main and replicas still have the correct state.
|
||||
# 4/ We kill the replica.
|
||||
# 5/ We observed that the replica result is in invalid state.
|
||||
# 6/ We start the replica again. We observe that indeed the replica has the replica state.
|
||||
# 7/ We observe that main has the replica ready.
|
||||
# 8/ We kill the replica again.
|
||||
# 9/ We add data to main.
|
||||
# 10/ We start the replica again. We observe that the replica has the same
|
||||
# 1/ We try to add a replica with reserved name which results in an exception <- Schema changed, there are no reserved names now
|
||||
# 2/ We check that all replicas have the correct state: they should all be ready.
|
||||
# 3/ We kill main.
|
||||
# 4/ We re-start main. We check that main indeed has the role main and replicas still have the correct state.
|
||||
# 5/ We kill the replica.
|
||||
# 6/ We observed that the replica result is in invalid state.
|
||||
# 7/ We start the replica again. We observe that indeed the replica has the replica state.
|
||||
# 8/ We observe that main has the replica ready.
|
||||
# 9/ We kill the replica again.
|
||||
# 10/ We add data to main.
|
||||
# 11/ We start the replica again. We observe that the replica has the same
|
||||
# data as main because it synced and added lost data.
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"replica": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--storage-recover-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE", "--replication-restore-state-on-startup=true"],
|
||||
"log_file": "replica.log",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
"data_directory": f"{data_directory.name}/replica",
|
||||
},
|
||||
"main": {
|
||||
@@ -580,16 +522,37 @@ def test_replication_role_recovery(connection):
|
||||
}
|
||||
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
replica_cursor = connection(7688, "replica").cursor()
|
||||
execute_and_fetch_all(replica_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, otherwise re-starting main would also execute these registration.
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA replica SYNC TO '127.0.0.1:10001';")
|
||||
|
||||
# 1/
|
||||
# When we restart the replica, it does not need this query anymore since it needs to remember state
|
||||
CONFIGURATION = {
|
||||
"replica": {
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE", "--replication-restore-state-on-startup=true"],
|
||||
"log_file": "replica.log",
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/replica",
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7687",
|
||||
"--log-level=TRACE",
|
||||
"--storage-recover-on-startup=true",
|
||||
"--replication-restore-state-on-startup=true",
|
||||
],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/main",
|
||||
},
|
||||
}
|
||||
# 1/ Obsolete, schema change, no longer a reserved name
|
||||
# with pytest.raises(mgclient.DatabaseError):
|
||||
# execute_and_fetch_all(cursor, "REGISTER REPLICA __replication_role SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 2/
|
||||
expected_data = {
|
||||
("replica", "127.0.0.1:10001", "sync", 0, 0, "ready"),
|
||||
}
|
||||
@@ -603,10 +566,10 @@ def test_replication_role_recovery(connection):
|
||||
|
||||
check_roles()
|
||||
|
||||
# 2/
|
||||
# 3/
|
||||
interactive_mg_runner.kill(CONFIGURATION, "main")
|
||||
|
||||
# 3/
|
||||
# 4/
|
||||
interactive_mg_runner.start(CONFIGURATION, "main")
|
||||
cursor = connection(7687, "main").cursor()
|
||||
check_roles()
|
||||
@@ -617,10 +580,10 @@ def test_replication_role_recovery(connection):
|
||||
actual_data = mg_sleep_and_assert(expected_data, retrieve_data)
|
||||
assert actual_data == expected_data
|
||||
|
||||
# 4/
|
||||
# 5/
|
||||
interactive_mg_runner.kill(CONFIGURATION, "replica")
|
||||
|
||||
# 5/
|
||||
# 6/
|
||||
expected_data = {
|
||||
("replica", "127.0.0.1:10001", "sync", 0, 0, "invalid"),
|
||||
}
|
||||
@@ -628,11 +591,11 @@ def test_replication_role_recovery(connection):
|
||||
|
||||
assert actual_data == expected_data
|
||||
|
||||
# 6/
|
||||
# 7/
|
||||
interactive_mg_runner.start(CONFIGURATION, "replica")
|
||||
check_roles()
|
||||
|
||||
# 7/
|
||||
# 8/
|
||||
expected_data = {
|
||||
("replica", "127.0.0.1:10001", "sync", 0, 0, "ready"),
|
||||
}
|
||||
@@ -640,14 +603,14 @@ def test_replication_role_recovery(connection):
|
||||
actual_data = mg_sleep_and_assert(expected_data, retrieve_data)
|
||||
assert actual_data == expected_data
|
||||
|
||||
# 8/
|
||||
# 9/
|
||||
interactive_mg_runner.kill(CONFIGURATION, "replica")
|
||||
|
||||
# 9/
|
||||
# 10/
|
||||
with pytest.raises(mgclient.DatabaseError):
|
||||
execute_and_fetch_all(cursor, "CREATE (n:First)")
|
||||
|
||||
# 10/
|
||||
# 11/
|
||||
interactive_mg_runner.start(CONFIGURATION, "replica")
|
||||
check_roles()
|
||||
|
||||
@@ -985,7 +948,7 @@ def test_attempt_to_write_data_on_main_when_async_replica_is_down():
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["async_replica2"].query(QUERY_TO_CHECK)
|
||||
|
||||
|
||||
def test_attempt_to_write_data_on_main_when_sync_replica_is_down(connection):
|
||||
def test_attempt_to_write_data_on_main_when_sync_replica_is_down():
|
||||
# Goal of this test is to check that main cannot write new data if a sync replica is down.
|
||||
# 0/ Start main and sync replicas.
|
||||
# 1/ Check status of replicas.
|
||||
@@ -995,48 +958,30 @@ def test_attempt_to_write_data_on_main_when_sync_replica_is_down(connection):
|
||||
# 5/ Check the status of replicas.
|
||||
# 6/ Restart the replica that was killed and check that it is up to date with main.
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"sync_replica1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica1.log",
|
||||
# We restart this replica so we want to set role manually
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/sync_replica1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"sync_replica2": {
|
||||
"args": ["--bolt-port", "7689", "--log-level", "TRACE"],
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica2.log",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10002;"],
|
||||
},
|
||||
"main": {
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup", "true"],
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup=true"],
|
||||
"log_file": "main.log",
|
||||
# need to do it manually
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/main",
|
||||
"setup_queries": [
|
||||
"REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';",
|
||||
"REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
sync_replica1_cursor = connection(7688, "sync_replica1_cursor").cursor()
|
||||
execute_and_fetch_all(sync_replica1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
main_cursor = connection(7687, "main").cursor()
|
||||
execute_and_fetch_all(main_cursor, "REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';")
|
||||
execute_and_fetch_all(main_cursor, "REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 1/
|
||||
expected_data = {
|
||||
("sync_replica1", "127.0.0.1:10001", "sync", 0, 0, "ready"),
|
||||
@@ -1181,7 +1126,7 @@ def test_attempt_to_create_indexes_on_main_when_async_replica_is_down():
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["async_replica2"].query(QUERY_TO_CHECK)
|
||||
|
||||
|
||||
def test_attempt_to_create_indexes_on_main_when_sync_replica_is_down(connection):
|
||||
def test_attempt_to_create_indexes_on_main_when_sync_replica_is_down():
|
||||
# Goal of this test is to check creation of new indexes/constraints when a sync replica is down.
|
||||
# 0/ Start main and sync replicas.
|
||||
# 1/ Check status of replicas.
|
||||
@@ -1191,21 +1136,11 @@ def test_attempt_to_create_indexes_on_main_when_sync_replica_is_down(connection)
|
||||
# 5/ Check the status of replicas.
|
||||
# 6/ Restart the replica that was killed and check that it is up to date with main.
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"sync_replica1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica1.log",
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/sync_replica1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"sync_replica2": {
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE"],
|
||||
@@ -1215,24 +1150,16 @@ def test_attempt_to_create_indexes_on_main_when_sync_replica_is_down(connection)
|
||||
"main": {
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup=true"],
|
||||
"log_file": "main.log",
|
||||
# Need to do it manually
|
||||
"setup_queries": [],
|
||||
"setup_queries": [
|
||||
"REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';",
|
||||
"REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
sync_replica1_cursor = connection(7688, "sync_replica1").cursor()
|
||||
execute_and_fetch_all(sync_replica1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, as we are setting replica manually because
|
||||
# of restart. Restart on replica would set role again.
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';")
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 1/
|
||||
expected_data = {
|
||||
("sync_replica1", "127.0.0.1:10001", "sync", 0, 0, "ready"),
|
||||
@@ -1299,7 +1226,7 @@ def test_attempt_to_create_indexes_on_main_when_sync_replica_is_down(connection)
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK)
|
||||
|
||||
|
||||
def test_trigger_on_create_before_commit_with_offline_sync_replica(connection):
|
||||
def test_trigger_on_create_before_commit_with_offline_sync_replica():
|
||||
# 0/ Start all.
|
||||
# 1/ Create the trigger
|
||||
# 2/ Create a node. We expect two nodes created (our Not_Magic and the Magic created by trigger).
|
||||
@@ -1310,23 +1237,11 @@ def test_trigger_on_create_before_commit_with_offline_sync_replica(connection):
|
||||
# 7/ Check that we have two nodes.
|
||||
# 8/ Re-start the replica and check it's online and that it has two nodes.
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"sync_replica1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica1.log",
|
||||
# Need to do it manually since we kill this replica
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/sync_replica1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"sync_replica2": {
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE"],
|
||||
@@ -1336,24 +1251,16 @@ def test_trigger_on_create_before_commit_with_offline_sync_replica(connection):
|
||||
"main": {
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup=true"],
|
||||
"log_file": "main.log",
|
||||
# Need to do it manually since we kill replica
|
||||
"setup_queries": [],
|
||||
"setup_queries": [
|
||||
"REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';",
|
||||
"REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
sync_replica1_cursor = connection(7688, "sync_replica1").cursor()
|
||||
execute_and_fetch_all(sync_replica1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, as we are setting replica manually because
|
||||
# of restart. Restart on replica would set role again.
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';")
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 1/
|
||||
QUERY_CREATE_TRIGGER = """
|
||||
CREATE TRIGGER exampleTrigger
|
||||
@@ -1418,7 +1325,7 @@ def test_trigger_on_create_before_commit_with_offline_sync_replica(connection):
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK)
|
||||
|
||||
|
||||
def test_trigger_on_update_before_commit_with_offline_sync_replica(connection):
|
||||
def test_trigger_on_update_before_commit_with_offline_sync_replica():
|
||||
# 0/ Start all.
|
||||
# 1/ Create the trigger
|
||||
# 2/ Create a node.
|
||||
@@ -1430,22 +1337,11 @@ def test_trigger_on_update_before_commit_with_offline_sync_replica(connection):
|
||||
# 8/ Check that we have two nodes.
|
||||
# 9/ Re-start the replica and check it's online and that it has two nodes.
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"sync_replica1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica1.log",
|
||||
# Need to do it manually
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/sync_replica1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"sync_replica2": {
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE"],
|
||||
@@ -1455,23 +1351,15 @@ def test_trigger_on_update_before_commit_with_offline_sync_replica(connection):
|
||||
"main": {
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup=true"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [],
|
||||
"setup_queries": [
|
||||
"REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';",
|
||||
"REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
sync_replica1_cursor = connection(7688, "sync_replica1").cursor()
|
||||
execute_and_fetch_all(sync_replica1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, as we are setting replica manually because
|
||||
# of restart. Restart on replica would set role again.
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';")
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 1/
|
||||
QUERY_CREATE_TRIGGER = """
|
||||
CREATE TRIGGER exampleTrigger
|
||||
@@ -1541,7 +1429,7 @@ def test_trigger_on_update_before_commit_with_offline_sync_replica(connection):
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK)
|
||||
|
||||
|
||||
def test_trigger_on_delete_before_commit_with_offline_sync_replica(connection):
|
||||
def test_trigger_on_delete_before_commit_with_offline_sync_replica():
|
||||
# 0/ Start all.
|
||||
# 1/ Create the trigger
|
||||
# 2/ Create a node.
|
||||
@@ -1553,22 +1441,11 @@ def test_trigger_on_delete_before_commit_with_offline_sync_replica(connection):
|
||||
# 8/ Check that we have one node.
|
||||
# 9/ Re-start the replica and check it's online and that it has one node, and the correct one.
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"sync_replica1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica1.log",
|
||||
# we need to set it manually
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/sync_replica1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"sync_replica2": {
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE"],
|
||||
@@ -1578,23 +1455,16 @@ def test_trigger_on_delete_before_commit_with_offline_sync_replica(connection):
|
||||
"main": {
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup=true"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [],
|
||||
"setup_queries": [
|
||||
"REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';",
|
||||
"REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
sync_replica1_cursor = connection(7688, "sync_replica1").cursor()
|
||||
execute_and_fetch_all(sync_replica1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, as we are setting replica manually because
|
||||
# of restart. Restart on replica would set role again.
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';")
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 1/
|
||||
QUERY_CREATE_TRIGGER = """
|
||||
CREATE TRIGGER exampleTrigger
|
||||
@@ -1603,7 +1473,7 @@ def test_trigger_on_delete_before_commit_with_offline_sync_replica(connection):
|
||||
"""
|
||||
interactive_mg_runner.MEMGRAPH_INSTANCES["main"].query(QUERY_CREATE_TRIGGER)
|
||||
res_from_main = interactive_mg_runner.MEMGRAPH_INSTANCES["main"].query("SHOW TRIGGERS;")
|
||||
assert len(res_from_main) == 1, f"Incorrect result: {res_from_main}"
|
||||
assert len(res_from_main) == 1, f"Incorect result: {res_from_main}"
|
||||
|
||||
# 2/
|
||||
QUERY_CREATE_NODE = "CREATE (p:Number {name:'Not_Magic', value:0})"
|
||||
@@ -1616,7 +1486,7 @@ def test_trigger_on_delete_before_commit_with_offline_sync_replica(connection):
|
||||
# 4/
|
||||
QUERY_TO_CHECK = "MATCH (node) return node;"
|
||||
res_from_main = interactive_mg_runner.MEMGRAPH_INSTANCES["main"].query(QUERY_TO_CHECK)
|
||||
assert len(res_from_main) == 1, f"Incorrect result: {res_from_main}"
|
||||
assert len(res_from_main) == 1, f"Incorect result: {res_from_main}"
|
||||
assert res_from_main[0][0].properties["name"] == "Node_created_by_trigger"
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica1"].query(QUERY_TO_CHECK)
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK)
|
||||
@@ -1669,7 +1539,7 @@ def test_trigger_on_delete_before_commit_with_offline_sync_replica(connection):
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK)
|
||||
|
||||
|
||||
def test_trigger_on_create_before_and_after_commit_with_offline_sync_replica(connection):
|
||||
def test_trigger_on_create_before_and_after_commit_with_offline_sync_replica():
|
||||
# 0/ Start all.
|
||||
# 1/ Create the triggers
|
||||
# 2/ Create a node. We expect three nodes created (1 node created + the two created by triggers).
|
||||
@@ -1680,22 +1550,11 @@ def test_trigger_on_create_before_and_after_commit_with_offline_sync_replica(con
|
||||
# 7/ Check that we have three nodes.
|
||||
# 8/ Re-start the replica and check it's online and that it has three nodes.
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"sync_replica1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica1.log",
|
||||
# we need to set it manually
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/sync_replica1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"sync_replica2": {
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE"],
|
||||
@@ -1705,23 +1564,16 @@ def test_trigger_on_create_before_and_after_commit_with_offline_sync_replica(con
|
||||
"main": {
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup=true"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [],
|
||||
"setup_queries": [
|
||||
"REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';",
|
||||
"REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
sync_replica1_cursor = connection(7688, "sync_replica1").cursor()
|
||||
execute_and_fetch_all(sync_replica1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, as we are setting replica manually because
|
||||
# of restart. Restart on replica would set role again.
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';")
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 1/
|
||||
QUERY_CREATE_TRIGGER_BEFORE = """
|
||||
CREATE TRIGGER exampleTriggerBefore
|
||||
@@ -1792,7 +1644,7 @@ def test_trigger_on_create_before_and_after_commit_with_offline_sync_replica(con
|
||||
assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK)
|
||||
|
||||
|
||||
def test_triggers_on_create_before_commit_with_offline_sync_replica(connection):
|
||||
def test_triggers_on_create_before_commit_with_offline_sync_replica():
|
||||
# 0/ Start all.
|
||||
# 1/ Create the two triggers
|
||||
# 2/ Create a node. We expect three nodes.
|
||||
@@ -1803,22 +1655,11 @@ def test_triggers_on_create_before_commit_with_offline_sync_replica(connection):
|
||||
# 7/ Check that we have three nodes.
|
||||
# 8/ Re-start the replica and check it's online and that it has two nodes.
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
CONFIGURATION = {
|
||||
"sync_replica1": {
|
||||
"args": [
|
||||
"--bolt-port",
|
||||
"7688",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"args": ["--bolt-port", "7688", "--log-level=TRACE"],
|
||||
"log_file": "sync_replica1.log",
|
||||
# we need to set it manually
|
||||
"setup_queries": [],
|
||||
"data_directory": f"{data_directory.name}/sync_replica1",
|
||||
"setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"],
|
||||
},
|
||||
"sync_replica2": {
|
||||
"args": ["--bolt-port", "7689", "--log-level=TRACE"],
|
||||
@@ -1828,23 +1669,16 @@ def test_triggers_on_create_before_commit_with_offline_sync_replica(connection):
|
||||
"main": {
|
||||
"args": ["--bolt-port", "7687", "--log-level=TRACE", "--storage-recover-on-startup=true"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [],
|
||||
"setup_queries": [
|
||||
"REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';",
|
||||
"REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(CONFIGURATION)
|
||||
|
||||
sync_replica1_cursor = connection(7688, "sync_replica1").cursor()
|
||||
execute_and_fetch_all(sync_replica1_cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;")
|
||||
|
||||
cursor = connection(7687, "main").cursor()
|
||||
|
||||
# We want to execute manually and not via the configuration, as we are setting replica manually because
|
||||
# of restart. Restart on replica would set role again.
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica1 SYNC TO '127.0.0.1:10001';")
|
||||
execute_and_fetch_all(cursor, "REGISTER REPLICA sync_replica2 SYNC TO '127.0.0.1:10002';")
|
||||
|
||||
# 1/
|
||||
QUERY_CREATE_TRIGGER_FIRST = """
|
||||
CREATE TRIGGER exampleTriggerFirst
|
||||
@@ -1990,5 +1824,4 @@ def test_replication_not_messed_up_by_ShowIndexInfo(connection):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-k", "test_basic_recovery"]))
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
|
||||
@@ -147,7 +147,6 @@ def test_auth_queries_on_replica(connection):
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -161,7 +160,6 @@ def test_auth_queries_on_replica(connection):
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -175,7 +173,6 @@ def test_auth_queries_on_replica(connection):
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -216,7 +213,6 @@ def test_manual_users_recovery(connection):
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -231,7 +227,6 @@ def test_manual_users_recovery(connection):
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -246,7 +241,6 @@ def test_manual_users_recovery(connection):
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -293,7 +287,6 @@ def test_env_users_recovery(connection):
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -307,7 +300,6 @@ def test_env_users_recovery(connection):
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -323,7 +315,6 @@ def test_env_users_recovery(connection):
|
||||
"username": "user1",
|
||||
"password": "password",
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -377,7 +368,6 @@ def test_manual_roles_recovery(connection):
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -392,7 +382,6 @@ def test_manual_roles_recovery(connection):
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -409,7 +398,6 @@ def test_manual_roles_recovery(connection):
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -467,7 +455,6 @@ def test_auth_config_recovery(connection):
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -488,7 +475,6 @@ def test_auth_config_recovery(connection):
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -511,7 +497,6 @@ def test_auth_config_recovery(connection):
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -565,7 +550,6 @@ def test_auth_replication(connection):
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -579,7 +563,6 @@ def test_auth_replication(connection):
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -593,7 +576,6 @@ def test_auth_replication(connection):
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
|
||||
@@ -16,7 +16,6 @@ import sys
|
||||
import tempfile
|
||||
import time
|
||||
from functools import partial
|
||||
from typing import Any, Dict
|
||||
|
||||
import interactive_mg_runner
|
||||
import mgclient
|
||||
@@ -34,78 +33,32 @@ interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactiv
|
||||
BOLT_PORTS = {"main": 7687, "replica_1": 7688, "replica_2": 7689}
|
||||
REPLICATION_PORTS = {"replica_1": 10001, "replica_2": 10002}
|
||||
|
||||
|
||||
def create_memgraph_instances_with_role_recovery(data_directory: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level",
|
||||
"TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"log_file": "replica1.log",
|
||||
"data_directory": f"{data_directory}/replica_1",
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
"--replication-restore-state-on-startup",
|
||||
"true",
|
||||
"--data-recovery-on-startup",
|
||||
"false",
|
||||
],
|
||||
"log_file": "replica2.log",
|
||||
"data_directory": f"{data_directory}/replica_2",
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def do_manual_setting_up(connection):
|
||||
replica_1_cursor = connection(BOLT_PORTS["replica_1"], "replica_1").cursor()
|
||||
execute_and_fetch_all(
|
||||
replica_1_cursor, f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};"
|
||||
)
|
||||
|
||||
replica_2_cursor = connection(BOLT_PORTS["replica_2"], "replica_2").cursor()
|
||||
execute_and_fetch_all(
|
||||
replica_2_cursor, f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};"
|
||||
)
|
||||
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
execute_and_fetch_all(
|
||||
main_cursor, f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';"
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
main_cursor, f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';"
|
||||
)
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = {
|
||||
"replica_1": {
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
|
||||
"log_file": "replica1.log",
|
||||
"setup_queries": [f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};"],
|
||||
},
|
||||
"replica_2": {
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
|
||||
"log_file": "replica2.log",
|
||||
"setup_queries": [f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};"],
|
||||
},
|
||||
"main": {
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [
|
||||
f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';",
|
||||
f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
TEMP_DIR = tempfile.TemporaryDirectory().name
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -117,7 +70,6 @@ MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY = {
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -129,7 +81,6 @@ MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY = {
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
@@ -216,12 +167,7 @@ def test_manual_databases_create_multitenancy_replication(connection):
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
|
||||
"log_file": "replica1.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -230,12 +176,7 @@ def test_manual_databases_create_multitenancy_replication(connection):
|
||||
],
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
|
||||
"log_file": "replica2.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -244,12 +185,7 @@ def test_manual_databases_create_multitenancy_replication(connection):
|
||||
],
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -306,12 +242,7 @@ def test_manual_databases_create_multitenancy_replication_branching(connection):
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
|
||||
"log_file": "replica1.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -324,12 +255,7 @@ def test_manual_databases_create_multitenancy_replication_branching(connection):
|
||||
],
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
|
||||
"log_file": "replica2.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -342,12 +268,7 @@ def test_manual_databases_create_multitenancy_replication_branching(connection):
|
||||
],
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -394,12 +315,7 @@ def test_manual_databases_create_multitenancy_replication_dirty_replica(connecti
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
|
||||
"log_file": "replica1.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -409,12 +325,7 @@ def test_manual_databases_create_multitenancy_replication_dirty_replica(connecti
|
||||
],
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
|
||||
"log_file": "replica2.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -424,12 +335,7 @@ def test_manual_databases_create_multitenancy_replication_dirty_replica(connecti
|
||||
],
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -471,12 +377,7 @@ def test_manual_databases_create_multitenancy_replication_main_behind(connection
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
|
||||
"log_file": "replica1.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -486,12 +387,7 @@ def test_manual_databases_create_multitenancy_replication_main_behind(connection
|
||||
],
|
||||
},
|
||||
"replica_2": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_2']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"],
|
||||
"log_file": "replica2.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -501,12 +397,7 @@ def test_manual_databases_create_multitenancy_replication_main_behind(connection
|
||||
],
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [
|
||||
f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';",
|
||||
@@ -547,12 +438,7 @@ def test_automatic_databases_create_multitenancy_replication(connection):
|
||||
# 3/ Validate replication of changes to A have arrived at REPLICA
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
do_manual_setting_up(connection)
|
||||
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
@@ -602,24 +488,14 @@ def test_automatic_databases_multitenancy_replication_predefined(connection):
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
|
||||
"log_file": "replica1.log",
|
||||
"setup_queries": [
|
||||
f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
|
||||
],
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -665,24 +541,14 @@ def test_automatic_databases_create_multitenancy_replication_dirty_main(connecti
|
||||
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = {
|
||||
"replica_1": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['replica_1']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"],
|
||||
"log_file": "replica1.log",
|
||||
"setup_queries": [
|
||||
f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};",
|
||||
],
|
||||
},
|
||||
"main": {
|
||||
"args": [
|
||||
"--experimental-enabled=system-replication",
|
||||
"--bolt-port",
|
||||
f"{BOLT_PORTS['main']}",
|
||||
"--log-level=TRACE",
|
||||
],
|
||||
"args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"],
|
||||
"log_file": "main.log",
|
||||
"setup_queries": [
|
||||
"CREATE DATABASE A;",
|
||||
@@ -720,15 +586,11 @@ def test_multitenancy_replication_restart_replica_w_fc(connection, replica_name)
|
||||
# 3/ Restart replica
|
||||
# 4/ Validate data on replica
|
||||
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
# 0/
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
do_manual_setting_up(connection)
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
execute_and_fetch_all(main_cursor, "CREATE DATABASE A;")
|
||||
execute_and_fetch_all(main_cursor, "CREATE DATABASE B;")
|
||||
|
||||
@@ -785,12 +647,7 @@ def test_multitenancy_replication_restart_replica_wo_fc(connection, replica_name
|
||||
# 4/ Validate data on replica
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
do_manual_setting_up(connection)
|
||||
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
@@ -876,11 +733,7 @@ def test_multitenancy_replication_drop_replica(connection, replica_name):
|
||||
# 4/ Validate data on replica
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
do_manual_setting_up(connection)
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
@@ -976,12 +829,7 @@ def test_automatic_databases_drop_multitenancy_replication(connection):
|
||||
# 5/ Check that the drop replicated
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
do_manual_setting_up(connection)
|
||||
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
@@ -1030,12 +878,7 @@ def test_drop_multitenancy_replication_restart_replica(connection, replica_name)
|
||||
# 4/ Validate data on replica
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
do_manual_setting_up(connection)
|
||||
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
@@ -1072,12 +915,7 @@ def test_multitenancy_drop_while_replica_using(connection):
|
||||
# 6/ Validate that the transaction is still active and working and that the replica2 is not pointing to anything
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
do_manual_setting_up(connection)
|
||||
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
@@ -1147,12 +985,7 @@ def test_multitenancy_drop_and_recreate_while_replica_using(connection):
|
||||
# 6/ Validate that the transaction is still active and working and that the replica2 is not pointing to anything
|
||||
|
||||
# 0/
|
||||
data_directory = tempfile.TemporaryDirectory()
|
||||
MEMGRAPH_INSTANCES_DESCRIPTION = create_memgraph_instances_with_role_recovery(data_directory.name)
|
||||
interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION)
|
||||
|
||||
do_manual_setting_up(connection)
|
||||
|
||||
main_cursor = connection(BOLT_PORTS["main"], "main").cursor()
|
||||
|
||||
# 1/
|
||||
|
||||
@@ -42,3 +42,21 @@ workloads:
|
||||
proc: "tests/e2e/streams/transformations/"
|
||||
args: ["streams/pulsar_streams_tests.py"]
|
||||
<<: *in_memory_cluster
|
||||
|
||||
- name: "Kafka streams start, stop and show for on-disk storage"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/streams/transformations/"
|
||||
args: ["streams/kafka_streams_tests.py"]
|
||||
<<: *disk_cluster
|
||||
|
||||
- name: "Streams with users for on-disk storage"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/streams/transformations/"
|
||||
args: ["streams/streams_owner_tests.py"]
|
||||
<<: *disk_cluster
|
||||
|
||||
- name: "Pulsar streams start, stop and show for on-disk storage"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/streams/transformations/"
|
||||
args: ["streams/pulsar_streams_tests.py"]
|
||||
<<: *disk_cluster
|
||||
|
||||
@@ -64,7 +64,7 @@ Feature: Functions
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (:Node {prop: TOBOOLEAN("t")});
|
||||
CREATE (:Node {prop: ToBoolean("t")});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
@@ -74,11 +74,11 @@ Feature: Functions
|
||||
| n.prop |
|
||||
| true |
|
||||
|
||||
Scenario: ToBoolean test 04:
|
||||
Scenario: ToBoolean test 03:
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (:Node {prop: TOBOOLEAN("f")});
|
||||
CREATE (:Node {prop: ToBoolean("f")});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
|
||||
@@ -185,20 +185,6 @@ Feature: Subqueries
|
||||
"""
|
||||
Then an error should be raised
|
||||
|
||||
Scenario: Subquery with an unbound variable
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (node1)
|
||||
CALL {
|
||||
MATCH (node2)
|
||||
WHERE node1.property > 0
|
||||
return 1 as state
|
||||
}
|
||||
return 1
|
||||
"""
|
||||
Then an error should be raised
|
||||
|
||||
Scenario: Subquery returning primitive but not aliased
|
||||
Given an empty graph
|
||||
And having executed
|
||||
|
||||
@@ -44,7 +44,7 @@ int main(int argc, char **argv) {
|
||||
memgraph::replication::ReplicationState repl_state(ReplicationStateRootPath(db_config));
|
||||
|
||||
memgraph::system::System system_state;
|
||||
memgraph::dbms::DbmsHandler dbms_handler(db_config, repl_state
|
||||
memgraph::dbms::DbmsHandler dbms_handler(db_config, system_state, repl_state
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
auth_, false
|
||||
|
||||
@@ -71,7 +71,8 @@ class TestEnvironment : public ::testing::Environment {
|
||||
memgraph::auth::Auth::Config{/* default */});
|
||||
system_state = std::make_unique<memgraph::system::System>();
|
||||
repl_state = std::make_unique<memgraph::replication::ReplicationState>(ReplicationStateRootPath(storage_conf));
|
||||
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, *repl_state.get(), *auth.get(), false);
|
||||
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, *system_state.get(), *repl_state.get(),
|
||||
*auth.get(), false);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
|
||||
@@ -55,7 +55,7 @@ class TestEnvironment : public ::testing::Environment {
|
||||
memgraph::auth::Auth::Config{/* default */});
|
||||
system_state = std::make_unique<memgraph::system::System>();
|
||||
repl_state = std::make_unique<memgraph::replication::ReplicationState>(ReplicationStateRootPath(storage_conf));
|
||||
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, *repl_state.get());
|
||||
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, *system_state.get(), *repl_state.get());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
|
||||
@@ -101,7 +101,7 @@ class MultiTenantTest : public ::testing::Test {
|
||||
explicit MinMemgraph(const memgraph::storage::Config &conf)
|
||||
: auth{conf.durability.storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}},
|
||||
repl_state{ReplicationStateRootPath(conf)},
|
||||
dbms{conf, repl_state, auth, true},
|
||||
dbms{conf, system, repl_state, auth, true},
|
||||
interpreter_context{{},
|
||||
&dbms,
|
||||
&repl_state,
|
||||
|
||||
@@ -113,7 +113,7 @@ struct MinMemgraph {
|
||||
MinMemgraph(const memgraph::storage::Config &conf)
|
||||
: auth{conf.durability.storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}},
|
||||
repl_state{ReplicationStateRootPath(conf)},
|
||||
dbms{conf, repl_state
|
||||
dbms{conf, system_, repl_state
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
auth, true
|
||||
@@ -124,7 +124,7 @@ struct MinMemgraph {
|
||||
repl_handler(repl_state, dbms
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
system_, auth
|
||||
&system_, auth
|
||||
#endif
|
||||
) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user