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:
Matija Santl
2019-05-24 13:26:24 +02:00
parent d7acf75a78
commit 87423eed06
17 changed files with 578 additions and 27 deletions

View File

@@ -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)

View File

@@ -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

View 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)

View File

@@ -0,0 +1,7 @@
{
"election_timeout_min": 200,
"election_timeout_max": 500,
"heartbeat_interval": 100,
"replication_timeout": 10000,
"log_size_snapshot_threshold": -1
}

View 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)

View 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;
}