diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index 63ee6460e..fc20f8b25 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -450,7 +450,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & return callback; } case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: { - callback.header = {"replication mode"}; + callback.header = {"replication role"}; callback.fn = [handler = ReplQueryHandler{interpreter_context->db}] { auto mode = handler.ShowReplicationRole(); switch (mode) { @@ -516,7 +516,6 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & typed_replica.emplace_back(TypedValue("async")); break; } - typed_replica.emplace_back(TypedValue(static_cast(replica.sync_mode))); if (replica.timeout) { typed_replica.emplace_back(TypedValue(*replica.timeout)); } else { diff --git a/tests/e2e/replication/CMakeLists.txt b/tests/e2e/replication/CMakeLists.txt index 37ab02725..9e462c4d7 100644 --- a/tests/e2e/replication/CMakeLists.txt +++ b/tests/e2e/replication/CMakeLists.txt @@ -5,3 +5,7 @@ target_link_libraries(memgraph__e2e__replication__constraints gflags mgclient mg add_executable(memgraph__e2e__replication__read_write_benchmark read_write_benchmark.cpp) target_link_libraries(memgraph__e2e__replication__read_write_benchmark gflags json mgclient mg-utils mg-io Threads::Threads) + +copy_e2e_python_files(replication_show common.py) +copy_e2e_python_files(replication_show conftest.py) +copy_e2e_python_files(replication_show show.py) diff --git a/tests/e2e/replication/common.py b/tests/e2e/replication/common.py new file mode 100644 index 000000000..2e99c90f0 --- /dev/null +++ b/tests/e2e/replication/common.py @@ -0,0 +1,26 @@ +# 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 mgclient +import typing + + +def execute_and_fetch_all( + cursor: mgclient.Cursor, query: str, params: dict = {} +) -> typing.List[tuple]: + cursor.execute(query, params) + return cursor.fetchall() + + +def connect(**kwargs) -> mgclient.Connection: + connection = mgclient.connect(**kwargs) + connection.autocommit = True + return connection diff --git a/tests/e2e/replication/conftest.py b/tests/e2e/replication/conftest.py new file mode 100644 index 000000000..f15a5efe5 --- /dev/null +++ b/tests/e2e/replication/conftest.py @@ -0,0 +1,44 @@ +# 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 pytest + +from common import execute_and_fetch_all, connect + + +# The fixture here is more complex because the connection has to be +# parameterized based on the test parameters (info has to be available on both +# sides). +# +# https://docs.pytest.org/en/latest/example/parametrize.html#indirect-parametrization +# is not an elegant/feasible solution here. +# +# The solution was independently developed and then I stumbled upon the same +# approach here https://stackoverflow.com/a/68286553/4888809 which I think is +# optimal. +@pytest.fixture(scope="function") +def connection(): + connection_holder = None + role_holder = None + + def inner_connection(port, role): + nonlocal connection_holder, role_holder + connection_holder = connect(host="localhost", port=port) + role_holder = role + return connection_holder + + yield inner_connection + + # Only main instance can be cleaned up because replicas do NOT accept + # writes. + if role_holder == "main": + cursor = connection_holder.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") diff --git a/tests/e2e/replication/show.py b/tests/e2e/replication/show.py new file mode 100755 index 000000000..ed7f0268b --- /dev/null +++ b/tests/e2e/replication/show.py @@ -0,0 +1,46 @@ +# 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 sys + +import pytest +from common import execute_and_fetch_all + + +@pytest.mark.parametrize( + "port, role", + [(7687, "main"), (7688, "replica"), (7689, "replica"), (7690, "replica")], +) +def test_show_replication_role(port, role, connection): + cursor = connection(port, role).cursor() + data = execute_and_fetch_all(cursor, "SHOW REPLICATION ROLE;") + assert cursor.description[0].name == "replication role" + assert data[0][0] == role + + +def test_show_replicas(connection): + cursor = connection(7687, "main").cursor() + actual_data = set(execute_and_fetch_all(cursor, "SHOW REPLICAS;")) + + expected_column_names = {"name", "socket_address", "sync_mode", "timeout"} + actual_column_names = {x.name for x in cursor.description} + assert expected_column_names == actual_column_names + + expected_data = { + ("replica_1", "127.0.0.1:10001", "sync", 0), + ("replica_2", "127.0.0.1:10002", "sync", 1.0), + ("replica_3", "127.0.0.1:10003", "async", None), + } + assert expected_data == actual_data + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/replication/workloads.yaml b/tests/e2e/replication/workloads.yaml index 39e1c9bdf..da3944539 100644 --- a/tests/e2e/replication/workloads.yaml +++ b/tests/e2e/replication/workloads.yaml @@ -46,4 +46,31 @@ workloads: args: [] <<: *template_cluster - + - name: "Show" + binary: "tests/e2e/pytest_runner.sh" + args: ["replication/show.py"] + cluster: + replica_1: + args: ["--bolt-port", "7688", "--log-level=TRACE"] + log_file: "replication-e2e-replica1.log" + setup_queries: ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"] + validation_queries: [] + replica_2: + args: ["--bolt-port", "7689", "--log-level=TRACE"] + log_file: "replication-e2e-replica2.log" + setup_queries: ["SET REPLICATION ROLE TO REPLICA WITH PORT 10002;"] + validation_queries: [] + replica_3: + args: ["--bolt-port", "7690", "--log-level=TRACE"] + log_file: "replication-e2e-replica3.log" + setup_queries: ["SET REPLICATION ROLE TO REPLICA WITH PORT 10003;"] + validation_queries: [] + main: + args: ["--bolt-port", "7687", "--log-level=TRACE"] + log_file: "replication-e2e-main.log" + setup_queries: [ + "REGISTER REPLICA replica_1 SYNC WITH TIMEOUT 0 TO '127.0.0.1:10001'", + "REGISTER REPLICA replica_2 SYNC WITH TIMEOUT 1 TO '127.0.0.1:10002'", + "REGISTER REPLICA replica_3 ASYNC TO '127.0.0.1:10003'" + ] + validation_queries: []