polardbxengine/router/tests/component/test_gr_notifications.cc

720 lines
28 KiB
C++

/*
Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef RAPIDJSON_NO_SIZETYPEDEFINE
// if we build within the server, it will set RAPIDJSON_NO_SIZETYPEDEFINE
// globally and require to include my_rapidjson_size_t.h
#include "my_rapidjson_size_t.h"
#endif
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/filereadstream.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/schema.h>
#include <rapidjson/stringbuffer.h>
#include "gmock/gmock.h"
#include "keyring/keyring_manager.h"
#include "mock_server_rest_client.h"
#include "mock_server_testutils.h"
#include "mysql_session.h"
#include "mysqlrouter/rest_client.h"
#include "router_component_system_layout.h"
#include "router_component_test.h"
#include "tcp_port_pool.h"
#include <protobuf_lite/mysqlx_notice.pb.h>
#include <chrono>
#include <fstream>
#include <stdexcept>
#include <thread>
using mysqlrouter::MySQLSession;
using ::testing::PrintToString;
using namespace std::chrono_literals;
namespace {
// default allocator for rapidJson (MemoryPoolAllocator) is broken for
// SparcSolaris
using JsonAllocator = rapidjson::CrtAllocator;
using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>, JsonAllocator>;
using JsonDocument =
rapidjson::GenericDocument<rapidjson::UTF8<>, JsonAllocator>;
using JsonStringBuffer =
rapidjson::GenericStringBuffer<rapidjson::UTF8<>, rapidjson::CrtAllocator>;
constexpr auto kTTL = 60s;
} // namespace
struct AsyncGRNotice {
// how many milliseconds after the client connects this Notice
// should be sent to the client
std::chrono::milliseconds send_offset_ms;
unsigned id;
bool is_local; // true = local, false = global
// GR Notice specific payload:
unsigned type;
std::string view_id;
// id of the node(s) on which given notice should get sent
std::vector<int> nodes;
};
class GrNotificationsTest : public RouterComponentTest {
protected:
std::string get_metadata_cache_section(
const std::string &use_gr_notifications,
const std::chrono::milliseconds ttl = kTTL) {
auto ttl_str = std::to_string(std::chrono::duration<double>(ttl).count());
return "[metadata_cache:test]\n"
"router_id=1\n"
"user=mysql_router1_user\n"
"metadata_cluster=test\n"
"connect_timeout=1\n"
"use_gr_notifications=" +
use_gr_notifications + "\n" + "ttl=" + ttl_str + "\n\n";
}
std::string get_metadata_cache_routing_section(uint16_t router_port,
const std::string &role,
const std::string &strategy) {
std::string result =
"[routing:test_default]\n"
"bind_port=" +
std::to_string(router_port) + "\n" +
"destinations=metadata-cache://test/default?role=" + role + "\n" +
"protocol=classic\n";
if (!strategy.empty())
result += std::string("routing_strategy=" + strategy + "\n");
return result;
}
auto &launch_router(const std::string &temp_test_dir,
const std::string &metadata_cache_section,
const std::string &routing_section,
const std::string &state_file_path,
const int expected_exit_code = 0) {
const std::string masterkey_file =
Path(temp_test_dir).join("master.key").str();
const std::string keyring_file = Path(temp_test_dir).join("keyring").str();
mysql_harness::init_keyring(keyring_file, masterkey_file, true);
mysql_harness::Keyring *keyring = mysql_harness::get_keyring();
keyring->store("mysql_router1_user", "password", "root");
mysql_harness::flush_keyring();
mysql_harness::reset_keyring();
// enable debug logs for better diagnostics in case of failure
std::string logger_section = "[logger]\nlevel = DEBUG\n";
// launch the router with metadata-cache configuration
auto default_section = get_DEFAULT_defaults();
default_section["keyring_path"] = keyring_file;
default_section["master_key_path"] = masterkey_file;
default_section["dynamic_state"] = state_file_path;
const std::string conf_file = create_config_file(
temp_test_dir,
logger_section + metadata_cache_section + routing_section,
&default_section);
auto &router = ProcessManager::launch_router(
{"-c", conf_file}, expected_exit_code, /*catch_stderr=*/true,
/*with_sudo=*/false);
return router;
}
void set_mock_metadata(const uint16_t http_port, const std::string &gr_id,
const std::vector<uint16_t> &gr_node_ports,
const std::vector<uint16_t> &gr_node_xports,
const bool send = false) {
JsonAllocator allocator;
gr_id_.reset(new JsonValue(gr_id.c_str(), gr_id.length(), allocator));
gr_nodes_.reset(new JsonValue(rapidjson::kArrayType));
size_t i{0};
for (auto &gr_node : gr_node_ports) {
JsonValue node(rapidjson::kArrayType);
node.PushBack(JsonValue((int)gr_node), allocator);
node.PushBack(JsonValue("ONLINE", strlen("ONLINE"), allocator),
allocator);
node.PushBack(JsonValue((int)gr_node_xports[i++]), allocator);
gr_nodes_->PushBack(node, allocator);
}
if (send) {
send_globals(http_port);
}
}
void set_mock_notices(const int node_id, const uint16_t http_port,
const std::vector<AsyncGRNotice> &async_notices,
const bool send = false) {
JsonAllocator allocator;
notices_.reset(new JsonValue(rapidjson::kArrayType));
for (const auto &async_notice : async_notices) {
// check if given notice is for this node
const auto &nodes = async_notice.nodes;
if (std::find(nodes.begin(), nodes.end(), node_id) == nodes.end())
continue;
JsonValue json_notice(rapidjson::kObjectType);
json_notice.AddMember(
"send_offset",
JsonValue((unsigned)async_notice.send_offset_ms.count()), allocator);
json_notice.AddMember("type", JsonValue(async_notice.id), allocator);
const std::string scope = async_notice.is_local ? "LOCAL" : "GLOBAL";
json_notice.AddMember("scope",
JsonValue(scope.c_str(), scope.length(), allocator),
allocator);
// gr notice specific payload
JsonValue json_notice_gr(rapidjson::kObjectType);
json_notice_gr.AddMember("type", JsonValue(async_notice.type), allocator);
const std::string view_id = async_notice.view_id;
json_notice_gr.AddMember(
"view_id", JsonValue(view_id.c_str(), view_id.length(), allocator),
allocator);
json_notice.AddMember("payload", json_notice_gr, allocator);
notices_->PushBack(json_notice, allocator);
}
if (send) {
send_globals(http_port);
}
}
void send_globals(const uint16_t http_port) {
JsonAllocator allocator;
JsonValue json_doc(rapidjson::kObjectType);
if (gr_id_) {
json_doc.AddMember("gr_id", *gr_id_, allocator);
}
if (gr_nodes_) {
json_doc.AddMember("gr_nodes", *gr_nodes_, allocator);
}
if (notices_) {
json_doc.AddMember("notices", *notices_, allocator);
}
const auto json_str = json_to_string(json_doc);
EXPECT_NO_THROW(MockServerRestClient(http_port).set_globals(json_str));
}
int get_ttl_queries_count(const std::string &json_string) {
rapidjson::Document json_doc;
json_doc.Parse(json_string.c_str());
if (json_doc.HasMember("md_query_count")) {
EXPECT_TRUE(json_doc["md_query_count"].IsInt());
return json_doc["md_query_count"].GetInt();
}
return 0;
}
std::string create_state_file(const std::string &dir,
const std::string &group_id,
const uint16_t node1_port,
const uint16_t node2_port) {
return RouterComponentTest::create_state_file(
dir,
"{"
"\"version\": \"1.0.0\","
"\"metadata-cache\": {"
"\"group-replication-id\": "
"\"" +
group_id +
"\","
"\"cluster-metadata-servers\": ["
"\"mysql://127.0.0.1:" +
std::to_string(node1_port) +
"\", "
"\"mysql://127.0.0.1:" +
std::to_string(node2_port) +
"\""
"]"
"}"
"}");
}
int wait_for_md_queries(int expected_md_queries_count, uint16_t http_port) {
int md_queries_count, retries{0};
do {
std::this_thread::sleep_for(100ms);
const std::string server_globals =
MockServerRestClient(http_port).get_globals_as_json_string();
md_queries_count = get_ttl_queries_count(server_globals);
} while (md_queries_count != expected_md_queries_count && retries++ < 50);
return md_queries_count;
}
TcpPortPool port_pool_;
std::unique_ptr<JsonValue> notices_;
std::unique_ptr<JsonValue> gr_id_;
std::unique_ptr<JsonValue> gr_nodes_;
};
struct GrNotificationsTestParams {
// how long do we wait for the router to operate before checking the
// metadata queries count
std::chrono::milliseconds router_uptime;
// how many metadata queries we expect over this period
int expected_md_queries_count;
// what Notices should be sent by the given cluster nodes at what
// time offsets
std::vector<AsyncGRNotice> notices;
GrNotificationsTestParams(std::chrono::milliseconds router_uptime_,
int expected_md_queries_count_,
std::vector<AsyncGRNotice> notices_)
: router_uptime(router_uptime_),
expected_md_queries_count(expected_md_queries_count_),
notices(notices_) {}
};
class GrNotificationsParamTest
: public GrNotificationsTest,
public ::testing::WithParamInterface<GrNotificationsTestParams> {
protected:
virtual void SetUp() { GrNotificationsTest::SetUp(); }
};
/**
* @test
* Verify that Router gets proper GR Notifications according to the cluster
* and Router configuration.
*/
TEST_P(GrNotificationsParamTest, GrNotification) {
const auto test_params = GetParam();
const auto async_notices = test_params.notices;
const std::string kGroupId = "3a0be5af-0022-11e8-9655-0800279e6a88";
TempDirectory temp_test_dir;
const unsigned kClusterNodesCount = 2;
std::vector<ProcessWrapper *> cluster_nodes;
std::vector<uint16_t> cluster_nodes_ports;
std::vector<uint16_t> cluster_nodes_xports;
std::vector<uint16_t> cluster_http_ports;
for (unsigned i = 0; i < kClusterNodesCount; ++i) {
cluster_nodes_ports.push_back(port_pool_.get_next_available());
cluster_nodes_xports.push_back(port_pool_.get_next_available());
cluster_http_ports.push_back(port_pool_.get_next_available());
}
SCOPED_TRACE(
"// Launch 2 server mocks that will act as our metadata servers");
const auto trace_file =
get_data_dir().join("metadata_dynamic_nodes.js").str();
std::vector<uint16_t> classic_ports, x_ports;
for (unsigned i = 0; i < kClusterNodesCount; ++i) {
cluster_nodes.push_back(&ProcessManager::launch_mysql_server_mock(
trace_file, cluster_nodes_ports[i], EXIT_SUCCESS, false,
cluster_http_ports[i], cluster_nodes_xports[i]));
ASSERT_NO_FATAL_FAILURE(
check_port_ready(*cluster_nodes[i], cluster_nodes_ports[i], 5000ms));
ASSERT_NO_FATAL_FAILURE(
check_port_ready(*cluster_nodes[i], cluster_nodes_xports[i], 5000ms));
ASSERT_TRUE(MockServerRestClient(cluster_http_ports[i])
.wait_for_rest_endpoint_ready())
<< cluster_nodes[i]->get_full_output();
SCOPED_TRACE("// Make our metadata server return 2 metadata servers");
classic_ports = {cluster_nodes_ports[0], cluster_nodes_ports[1]};
x_ports = {cluster_nodes_xports[0], cluster_nodes_xports[1]};
set_mock_metadata(cluster_http_ports[i], kGroupId, classic_ports, x_ports);
SCOPED_TRACE(
"// Make our metadata server to send GR notices at requested "
"time offsets");
set_mock_notices(i, cluster_http_ports[i], async_notices, /*send=*/true);
}
SCOPED_TRACE("// Create a router state file");
const std::string state_file =
create_state_file(temp_test_dir.name(), kGroupId, cluster_nodes_ports[0],
cluster_nodes_ports[1]);
SCOPED_TRACE(
"// Create a configuration file sections with high ttl so that "
"metadata updates were triggered by the GR notifications");
const std::string metadata_cache_section =
get_metadata_cache_section(/*use_gr_notifications=*/"1", kTTL);
const uint16_t router_port = port_pool_.get_next_available();
const std::string routing_section = get_metadata_cache_routing_section(
router_port, "PRIMARY", "first-available");
SCOPED_TRACE("// Launch ther router");
auto &router = launch_router(temp_test_dir.name(), metadata_cache_section,
routing_section, state_file);
std::this_thread::sleep_for(test_params.router_uptime);
// +1 is for expected initial metadata read that the router does at the
// beginning
const int expected_md_queries_count =
test_params.expected_md_queries_count + 1;
// we only expect initial ttl read (hence 1), because x-port is not valid
// there are not metadata refresh triggered by the notifications
int md_queries_count =
wait_for_md_queries(expected_md_queries_count, cluster_http_ports[0]);
ASSERT_EQ(expected_md_queries_count, md_queries_count)
<< "mock[0]: " << cluster_nodes[0]->get_full_output() << "\n"
<< "mock[1]: " << cluster_nodes[1]->get_full_output() << "\n"
<< "router: " << router.get_full_logfile();
}
INSTANTIATE_TEST_CASE_P(
CheckNoticesHandlingIsOk, GrNotificationsParamTest,
::testing::Values(
// 0) single notification received from single (first) node
// we expect 1 metadata cache update
GrNotificationsTestParams(
500ms, 1,
{{100ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_VIEW_CHANGE,
"abcdefg",
{0}}}),
// 1) 3 notifications with the same view id, again only 1
// mdc update expected
GrNotificationsTestParams(
500ms, 1,
{{100ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_VIEW_CHANGE,
"abcdefg",
{0}},
{200ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBER_STATE_CHANGE,
"abcdefg",
{0}},
{300ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_QUORUM_LOSS,
"abcdefg",
{0}}}),
// 2) 3 notifications; 2 have different view id this time so the
// refresh should be triggered twice
GrNotificationsTestParams(
1000ms, 2,
{{100ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_VIEW_CHANGE,
"abcdefg",
{0}},
{400ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBER_STATE_CHANGE,
"abcdefg",
{0}},
{700ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_QUORUM_LOSS,
"hijklmn",
{0}}}),
// 3) 2 notifications on both nodes with the same view id
GrNotificationsTestParams(
1500ms, 1,
{{100ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_VIEW_CHANGE,
"abcdefg",
{0, 1}}}),
// 4) 2 notifications on both nodes with different view ids
GrNotificationsTestParams(
700ms, 2,
{{100ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_VIEW_CHANGE,
"abcdefg",
{0}},
{500ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBER_ROLE_CHANGE,
"hijklmn",
{0}}})));
class GrNotificationsTestNoParam : public GrNotificationsTest {
public:
virtual void SetUp() { GrNotificationsTest::SetUp(); }
};
/**
* @test
* Verify that Router operates properly when it can't connect to the
* x-port.
*/
TEST_F(GrNotificationsTestNoParam, GrNotificationNoXPort) {
const std::string kGroupId = "3a0be5af-0022-11e8-9655-0800279e6a88";
TempDirectory temp_test_dir;
const unsigned CLUSTER_NODES = 2;
std::vector<ProcessWrapper *> cluster_nodes;
std::vector<uint16_t> cluster_nodes_ports;
std::vector<uint16_t> reserved_nodes_xports;
std::vector<uint16_t> cluster_http_ports;
for (unsigned i = 0; i < CLUSTER_NODES; ++i) {
cluster_nodes_ports.push_back(port_pool_.get_next_available());
reserved_nodes_xports.push_back(port_pool_.get_next_available());
cluster_http_ports.push_back(port_pool_.get_next_available());
}
SCOPED_TRACE(
"// Launch 2 server mocks that will act as our metadata servers");
const auto trace_file =
get_data_dir().join("metadata_dynamic_nodes.js").str();
std::vector<uint16_t> classic_ports, x_ports;
for (unsigned i = 0; i < CLUSTER_NODES; ++i) {
cluster_nodes.push_back(&ProcessManager::launch_mysql_server_mock(
trace_file, cluster_nodes_ports[i], EXIT_SUCCESS, false,
cluster_http_ports[i]));
ASSERT_NO_FATAL_FAILURE(
check_port_ready(*cluster_nodes[i], cluster_nodes_ports[i], 5000ms));
ASSERT_TRUE(MockServerRestClient(cluster_http_ports[i])
.wait_for_rest_endpoint_ready())
<< cluster_nodes[i]->get_full_output();
SCOPED_TRACE("// Make our metadata server return 2 metadata servers");
classic_ports = {cluster_nodes_ports[0], cluster_nodes_ports[1]};
x_ports = {reserved_nodes_xports[0], reserved_nodes_xports[1]};
set_mock_metadata(cluster_http_ports[i], kGroupId, classic_ports, x_ports);
set_mock_notices(
i, cluster_http_ports[i],
{{100ms,
Mysqlx::Notice::Frame::GROUP_REPLICATION_STATE_CHANGED,
true,
Mysqlx::Notice::
GroupReplicationStateChanged_Type_MEMBERSHIP_VIEW_CHANGE,
"abcdefg",
{0}}},
true);
}
SCOPED_TRACE("// Create a router state file");
const std::string state_file =
create_state_file(temp_test_dir.name(), kGroupId, cluster_nodes_ports[0],
cluster_nodes_ports[1]);
SCOPED_TRACE("// Create a configuration file sections with high ttl");
const std::string metadata_cache_section =
get_metadata_cache_section(/*use_gr_notifications=*/"1", kTTL);
const uint16_t router_port = port_pool_.get_next_available();
const std::string routing_section = get_metadata_cache_routing_section(
router_port, "PRIMARY", "first-available");
SCOPED_TRACE("// Launch the router");
auto &router = launch_router(temp_test_dir.name(), metadata_cache_section,
routing_section, state_file);
SCOPED_TRACE("// Let the router run for a while");
std::this_thread::sleep_for(500ms);
// we only expect initial ttl read (hence 1), because x-port is not valid
// there are not metadata refresh triggered by the notifications
int md_queries_count = wait_for_md_queries(1, cluster_http_ports[0]);
ASSERT_EQ(1, md_queries_count)
<< "mock[0]: " << cluster_nodes[0]->get_full_output() << "\n"
<< "mock[1]: " << cluster_nodes[1]->get_full_output() << "\n"
<< "router: " << router.get_full_logfile();
// we expect that the router will not be able to connect to both nodes on the
// x-port. that can take up to 2 * 10s as 10 seconds is a timeout we use for
// the x connect. If the port that we try to connect to is not used/blocked by
// anyone that should error out right away but that is not a case sometimes on
// Solaris so in the worst case we will wait 20 seconds here for the router to
// exit
ASSERT_FALSE(router.send_shutdown_event());
check_exit_code(router, EXIT_SUCCESS, 22000ms);
}
/**
* @test Verify that killing one of the nodes (hence disconnecting the
* notification listener is triggering the metadata refresh.
*/
TEST_F(GrNotificationsTestNoParam, GrNotificationXPortConnectionFailure) {
const std::string kGroupId = "3a0be5af-0022-11e8-9655-0800279e6a88";
TempDirectory temp_test_dir;
const unsigned CLUSTER_NODES = 2;
std::vector<ProcessWrapper *> cluster_nodes;
std::vector<uint16_t> cluster_nodes_ports, cluster_nodes_xports,
cluster_http_ports;
for (unsigned i = 0; i < CLUSTER_NODES; ++i) {
cluster_nodes_ports.push_back(port_pool_.get_next_available());
cluster_nodes_xports.push_back(port_pool_.get_next_available());
cluster_http_ports.push_back(port_pool_.get_next_available());
}
SCOPED_TRACE(
"// Launch 2 server mocks that will act as our metadata servers");
const auto trace_file =
get_data_dir().join("metadata_dynamic_nodes.js").str();
std::vector<uint16_t> classic_ports, x_ports;
for (unsigned i = 0; i < CLUSTER_NODES; ++i) {
cluster_nodes.push_back(&ProcessManager::launch_mysql_server_mock(
trace_file, cluster_nodes_ports[i], EXIT_SUCCESS, false,
cluster_http_ports[i], cluster_nodes_xports[i]));
ASSERT_NO_FATAL_FAILURE(
check_port_ready(*cluster_nodes[i], cluster_nodes_ports[i], 5000ms));
ASSERT_TRUE(MockServerRestClient(cluster_http_ports[i])
.wait_for_rest_endpoint_ready())
<< cluster_nodes[i]->get_full_output();
SCOPED_TRACE("// Make our metadata server return 2 metadata servers");
classic_ports = {cluster_nodes_ports[0], cluster_nodes_ports[1]};
x_ports = {cluster_nodes_xports[0], cluster_nodes_xports[1]};
set_mock_metadata(cluster_http_ports[i], kGroupId, classic_ports, x_ports,
true);
}
SCOPED_TRACE("// Create a router state file");
const std::string state_file =
create_state_file(temp_test_dir.name(), kGroupId, cluster_nodes_ports[0],
cluster_nodes_ports[1]);
SCOPED_TRACE("// Create a configuration file sections with high ttl");
const std::string metadata_cache_section =
get_metadata_cache_section(/*use_gr_notifications=*/"1", kTTL);
const uint16_t router_port = port_pool_.get_next_available();
const std::string routing_section = get_metadata_cache_routing_section(
router_port, "PRIMARY", "first-available");
SCOPED_TRACE("// Launch ther router");
auto &router = launch_router(temp_test_dir.name(), metadata_cache_section,
routing_section, state_file);
std::this_thread::sleep_for(1s);
EXPECT_TRUE(cluster_nodes[1]->kill() == 0)
<< cluster_nodes[1]->get_full_output();
std::this_thread::sleep_for(1s);
// we only xpect initial ttl read plus the one caused by the x-protocol
// notifier connection to the node we killed
int md_queries_count = wait_for_md_queries(2, cluster_http_ports[0]);
ASSERT_EQ(2, md_queries_count)
<< "mock[0]: " << cluster_nodes[0]->get_full_output() << "\n"
<< "mock[1]: " << cluster_nodes[1]->get_full_output() << "\n"
<< "router: " << router.get_full_logfile();
}
struct ConfErrorTestParams {
std::string use_gr_notifications_option_value;
std::string expected_error_message;
};
class GrNotificationsConfErrorTest
: public GrNotificationsTest,
public ::testing::WithParamInterface<ConfErrorTestParams> {
protected:
virtual void SetUp() { GrNotificationsTest::SetUp(); }
};
/**
* @test
* Verify that Router returns with a proper error message when
* invalid GR notification options is configured.
*/
TEST_P(GrNotificationsConfErrorTest, GrNotificationConfError) {
const auto test_params = GetParam();
const std::string kGroupId = "3a0be5af-0022-11e8-9655-0800279e6a88";
TempDirectory temp_test_dir;
SCOPED_TRACE("// Create a router configuration file");
const std::string metadata_cache_section = get_metadata_cache_section(
/*use_gr_notifications=*/test_params.use_gr_notifications_option_value,
kTTL);
const uint16_t router_port = port_pool_.get_next_available();
const std::string routing_section = get_metadata_cache_routing_section(
router_port, "PRIMARY", "first-available");
SCOPED_TRACE("// Launch ther router");
// clang-format off
const std::string state_file =
RouterComponentTest::create_state_file(temp_test_dir.name(),
"{"
"\"version\": \"1.0.0\","
"\"metadata-cache\": {"
"\"group-replication-id\": " "\"" + kGroupId + "\","
"\"cluster-metadata-servers\": []"
"}"
"}");
// clang-format on
auto &router = launch_router(temp_test_dir.name(), metadata_cache_section,
routing_section, state_file, EXIT_FAILURE);
const auto wait_for_process_exit_timeout{10000ms};
check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout);
const std::string log_content = router.get_full_logfile();
EXPECT_NE(log_content.find(test_params.expected_error_message),
log_content.npos)
<< log_content;
}
INSTANTIATE_TEST_CASE_P(
CheckNoticesConfError, GrNotificationsConfErrorTest,
::testing::Values(
ConfErrorTestParams{"2",
"Configuration error: option use_gr_notifications "
"in [metadata_cache:test] needs value between 0 "
"and 1 inclusive, was '2'"},
ConfErrorTestParams{"-1",
"Configuration error: option use_gr_notifications "
"in [metadata_cache:test] needs value between 0 "
"and 1 inclusive, was '-1'"},
ConfErrorTestParams{"invalid",
"Configuration error: option use_gr_notifications "
"in [metadata_cache:test] needs value between 0 "
"and 1 inclusive, was 'invalid'"}));
int main(int argc, char *argv[]) {
init_windows_sockets();
ProcessManager::set_origin(Path(argv[0]).dirname());
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}