Add constraint in HA
Summary: HA should now support constraints in the same way the SM version does. I only tested this thing manually, but I plan to add a new integration test for this also. Reviewers: ipaljak, vkasljevic, mferencevic Reviewed By: ipaljak, mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2083
This commit is contained in:
@@ -19,6 +19,9 @@ add_subdirectory(distributed)
|
||||
# distributed ha/basic binaries
|
||||
add_subdirectory(ha/basic)
|
||||
|
||||
# distributed ha/constraints binaries
|
||||
add_subdirectory(ha/constraints)
|
||||
|
||||
# distributed ha/index binaries
|
||||
add_subdirectory(ha/index)
|
||||
|
||||
|
||||
@@ -82,6 +82,16 @@
|
||||
- ../../../../build_debug/memgraph_ha # memgraph ha binary
|
||||
- ../../../../build_debug/tests/integration/ha/basic/tester # tester binary
|
||||
|
||||
- name: integration__ha_constraints
|
||||
cd: ha/constraints
|
||||
commands: ./runner.py
|
||||
infiles:
|
||||
- runner.py # runner script
|
||||
- raft.json # raft configuration
|
||||
- ../ha_test.py # raft test base module
|
||||
- ../../../../build_debug/memgraph_ha # memgraph ha binary
|
||||
- ../../../../build_debug/tests/integration/ha/constraints/tester # tester binary
|
||||
|
||||
- name: integration__ha_index
|
||||
cd: ha/index
|
||||
commands: ./runner.py
|
||||
|
||||
6
tests/integration/ha/constraints/CMakeLists.txt
Normal file
6
tests/integration/ha/constraints/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
set(target_name memgraph__integration__ha_constraints)
|
||||
set(tester_target_name ${target_name}__tester)
|
||||
|
||||
add_executable(${tester_target_name} tester.cpp)
|
||||
set_target_properties(${tester_target_name} PROPERTIES OUTPUT_NAME tester)
|
||||
target_link_libraries(${tester_target_name} mg-utils mg-communication)
|
||||
7
tests/integration/ha/constraints/raft.json
Normal file
7
tests/integration/ha/constraints/raft.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"election_timeout_min": 200,
|
||||
"election_timeout_max": 500,
|
||||
"heartbeat_interval": 100,
|
||||
"replication_timeout": 10000,
|
||||
"log_size_snapshot_threshold": -1
|
||||
}
|
||||
122
tests/integration/ha/constraints/runner.py
Executable file
122
tests/integration/ha/constraints/runner.py
Executable file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", "..", ".."))
|
||||
|
||||
# append parent directory
|
||||
sys.path.append(os.path.join(SCRIPT_DIR, ".."))
|
||||
|
||||
from ha_test import HaTestBase
|
||||
|
||||
|
||||
class HaIndexTest(HaTestBase):
|
||||
def execute_step(self, step, property_value=None, expected_status=None,
|
||||
expected_result=None):
|
||||
if step == "create":
|
||||
print("Executing create query")
|
||||
client = subprocess.Popen([self.tester_binary, "--step", "create",
|
||||
"--cluster_size", str(self.cluster_size)])
|
||||
|
||||
elif step == "drop":
|
||||
print("Executing drop query")
|
||||
client = subprocess.Popen([self.tester_binary, "--step", "drop",
|
||||
"--cluster_size", str(self.cluster_size)])
|
||||
elif step == "add_node":
|
||||
print("Executing add_node query ")
|
||||
client = subprocess.Popen([self.tester_binary, "--step", "add_node",
|
||||
"--cluster_size", str(self.cluster_size), "--expected_status",
|
||||
str(expected_status), "--property_value", str(property_value)])
|
||||
|
||||
elif step == "check":
|
||||
print("Executing check query")
|
||||
client = subprocess.Popen([self.tester_binary, "--step", "check",
|
||||
"--cluster_size", str(self.cluster_size), "--expected_result",
|
||||
str(expected_result)])
|
||||
else:
|
||||
raise ValueError("Invalid step argument: " + step)
|
||||
|
||||
# Check what happened with query execution.
|
||||
try:
|
||||
code = client.wait(timeout=30)
|
||||
except subprocess.TimeoutExpired as e:
|
||||
print("HA client timed out!")
|
||||
client.kill()
|
||||
return 1
|
||||
|
||||
return code
|
||||
|
||||
|
||||
def execute(self):
|
||||
self.start_cluster()
|
||||
num_nodes = 1
|
||||
|
||||
assert self.execute_step("add_node", expected_status=0, \
|
||||
property_value=num_nodes) == 0, \
|
||||
"Error while executing add_node query"
|
||||
|
||||
assert self.execute_step("create") == 0, \
|
||||
"Error while executing create query"
|
||||
|
||||
assert self.execute_step("check", expected_result=num_nodes) == 0, \
|
||||
"Error while executing check query"
|
||||
|
||||
for i in range(self.cluster_size):
|
||||
# Kill worker.
|
||||
print("Killing worker {}".format(i))
|
||||
self.kill_worker(i)
|
||||
|
||||
assert self.execute_step("add_node", expected_status=1, \
|
||||
property_value=num_nodes) == 0, \
|
||||
"Error while executing add_node query"
|
||||
|
||||
assert self.execute_step("add_node", expected_status=0, \
|
||||
property_value=num_nodes + 1) == 0, \
|
||||
"Error while executing add_node query"
|
||||
|
||||
num_nodes += 1
|
||||
|
||||
# Bring worker back to life.
|
||||
print("Starting worker {}".format(i))
|
||||
self.start_worker(i)
|
||||
|
||||
assert self.execute_step("drop") == 0, \
|
||||
"Error while executing drop query"
|
||||
|
||||
assert self.execute_step("check", expected_result=num_nodes) == 0, \
|
||||
"Error while executing check query"
|
||||
|
||||
|
||||
def find_correct_path(path):
|
||||
f = os.path.join(PROJECT_DIR, "build", path)
|
||||
if not os.path.exists(f):
|
||||
f = os.path.join(PROJECT_DIR, "build_debug", path)
|
||||
return f
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
memgraph_binary = find_correct_path("memgraph_ha")
|
||||
tester_binary = find_correct_path(os.path.join("tests", "integration", "ha",
|
||||
"constraints", "tester"))
|
||||
|
||||
raft_config_file = os.path.join(PROJECT_DIR, "tests", "integration", "ha",
|
||||
"constraints", "raft.json")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--memgraph", default=memgraph_binary)
|
||||
parser.add_argument("--raft_config_file", default=raft_config_file)
|
||||
args = parser.parse_args()
|
||||
|
||||
for cluster_size in [3, 5]:
|
||||
print("\033[1;36m~~ Executing test with cluster size: %d~~\033[0m" % (cluster_size))
|
||||
HaIndexTest(
|
||||
args.memgraph, tester_binary, args.raft_config_file, cluster_size)
|
||||
print("\033[1;32m~~ The test finished successfully ~~\033[0m")
|
||||
|
||||
sys.exit(0)
|
||||
99
tests/integration/ha/constraints/tester.cpp
Normal file
99
tests/integration/ha/constraints/tester.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "communication/bolt/ha_client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_int32(cluster_size, 3, "Size of the raft cluster.");
|
||||
DEFINE_int32(num_retries, 20, "Number of (leader) execution retries.");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
DEFINE_string(step, "",
|
||||
"The step to execute (available: create, check, add_node, drop");
|
||||
DEFINE_int32(property_value, 0, "Value of the property when creating a node.");
|
||||
DEFINE_int32(expected_status, 0,
|
||||
"Expected query execution status when creating a node, 0 is success");
|
||||
DEFINE_int32(expected_result, 0, "Expected query result");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
|
||||
communication::Init();
|
||||
|
||||
try {
|
||||
std::vector<io::network::Endpoint> endpoints;
|
||||
for (int i = 0; i < FLAGS_cluster_size; ++i) {
|
||||
uint16_t port = FLAGS_port + i;
|
||||
io::network::Endpoint endpoint{FLAGS_address, port};
|
||||
endpoints.push_back(endpoint);
|
||||
}
|
||||
|
||||
std::chrono::milliseconds retry_delay(1000);
|
||||
communication::ClientContext context(FLAGS_use_ssl);
|
||||
communication::bolt::HAClient client(endpoints, &context, FLAGS_username,
|
||||
FLAGS_password, FLAGS_num_retries,
|
||||
retry_delay);
|
||||
|
||||
if (FLAGS_step == "create") {
|
||||
client.Execute("create constraint on (n:Node) assert n.prop is unique",
|
||||
{});
|
||||
return 0;
|
||||
} else if (FLAGS_step == "drop") {
|
||||
client.Execute("drop constraint on (n:Node) assert n.prop is unique", {});
|
||||
return 0;
|
||||
} else if (FLAGS_step == "add_node") {
|
||||
client.Execute(
|
||||
fmt::format("create (:Node{{prop:{}}})", FLAGS_property_value), {});
|
||||
|
||||
if (FLAGS_expected_status == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
LOG(WARNING) << "Query execution should've fail but it didn't.";
|
||||
}
|
||||
|
||||
} else if (FLAGS_step == "check") {
|
||||
auto result = client.Execute("match (n) return n", {});
|
||||
|
||||
if (result.records.size() != FLAGS_expected_result) {
|
||||
LOG(WARNING) << "Unexpected number of nodes: "
|
||||
<< "expected " << FLAGS_expected_result
|
||||
<< ", got " << result.records.size();
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
LOG(FATAL) << "Unexpected client step!";
|
||||
}
|
||||
} catch (const communication::bolt::ClientQueryException &e) {
|
||||
// Sometimes we expect the query to fail, so we need to handle this as
|
||||
// success.
|
||||
if (FLAGS_expected_status == 0) {
|
||||
LOG(WARNING) << "There was some transient error during query execution.";
|
||||
} else {
|
||||
LOG(INFO) << "Query execution failed as expected, message: " << e.what();
|
||||
return 0;
|
||||
}
|
||||
} catch (const communication::bolt::ClientFatalException &) {
|
||||
LOG(WARNING) << "Failed to communicate with the leader.";
|
||||
} catch (const utils::BasicException &) {
|
||||
LOG(WARNING) << "Error while executing query.";
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user