polardbxengine/router/tests/component/test_routing_strategy.cc

753 lines
30 KiB
C++

/*
Copyright (c) 2017, 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
*/
#include <chrono>
#include <thread>
#include <gmock/gmock.h>
#include "mock_server_rest_client.h"
#include "mock_server_testutils.h"
#include "mysql_session.h"
#include "rest_metadata_client.h"
#include "router_component_test.h"
#include "tcp_port_pool.h"
/**
* assert std::error_code has no 'error'
*/
#define ASSERT_NO_ERROR(expr) \
ASSERT_THAT(expr, ::testing::Eq(std::error_code{})) << expr.message()
using mysqlrouter::MySQLSession;
static const std::string kRestApiUsername("someuser");
static const std::string kRestApiPassword("somepass");
class RouterRoutingStrategyTest : public RouterComponentTest {
protected:
virtual void SetUp() {
RouterComponentTest::SetUp();
// Valgrind needs way more time
if (getenv("WITH_VALGRIND")) {
wait_for_cache_ready_timeout = 5000;
wait_for_process_exit_timeout = 20000;
wait_for_static_ready_timeout = 1000;
}
}
std::string get_metadata_cache_section(unsigned metadata_server_port) {
return "[metadata_cache:test]\n"
"router_id=1\n"
"bootstrap_server_addresses=mysql://localhost:" +
std::to_string(metadata_server_port) +
"\n"
"user=mysql_router1_user\n"
"metadata_cluster=test\n"
"ttl=300\n\n";
}
std::string get_static_routing_section(
unsigned router_port, const std::vector<uint16_t> &destinations,
const std::string &strategy, const std::string &mode = "") {
std::string result =
"[routing:test_default]\n"
"bind_port=" +
std::to_string(router_port) + "\n" + "protocol=classic\n";
result += "destinations=";
for (size_t i = 0; i < destinations.size(); ++i) {
result += "127.0.0.1:" + std::to_string(destinations[i]);
if (i != destinations.size() - 1) {
result += ",";
}
}
result += "\n";
if (!strategy.empty())
result += std::string("routing_strategy=" + strategy + "\n");
if (!mode.empty()) result += std::string("mode=" + mode + "\n");
return result;
}
// for error scenarios allow empty values
std::string get_static_routing_section_error(
unsigned router_port, const std::vector<unsigned> &destinations,
const std::string &strategy, const std::string &mode) {
std::string result =
"[routing:test_default]\n"
"bind_port=" +
std::to_string(router_port) + "\n" + "protocol=classic\n";
result += "destinations=";
for (size_t i = 0; i < destinations.size(); ++i) {
result += "localhost:" + std::to_string(destinations[i]);
if (i != destinations.size() - 1) {
result += ",";
}
}
result += "\n";
result += std::string("routing_strategy=" + strategy + "\n");
result += std::string("mode=" + mode + "\n");
return result;
}
std::string get_metadata_cache_routing_section(unsigned router_port,
const std::string &role,
const std::string &strategy,
const std::string &mode = "") {
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");
if (!mode.empty()) result += std::string("mode=" + mode + "\n");
return result;
}
std::string get_monitoring_section(unsigned monitoring_port,
const std::string &config_dir) {
std::string passwd_filename =
mysql_harness::Path(config_dir).join("users").str();
{
auto &cmd = launch_command(get_origin().join("mysqlrouter_passwd").str(),
{"set", passwd_filename, kRestApiUsername},
EXIT_SUCCESS, true);
cmd.register_response("Please enter password", kRestApiPassword + "\n");
check_exit_code(cmd, EXIT_SUCCESS);
}
return "[rest_api]\n"
"[rest_metadata_cache]\n"
"require_realm=somerealm\n"
"[http_auth_realm:somerealm]\n"
"backend=somebackend\n"
"method=basic\n"
"name=somerealm\n"
"[http_auth_backend:somebackend]\n"
"backend=file\n"
"filename=" +
passwd_filename +
"\n"
"[http_server]\n"
"port=" +
std::to_string(monitoring_port) + "\n";
}
// need to return void to be able to use ASSERT_ macros
void connect_client_and_query_port(unsigned router_port,
std::string &out_port,
bool should_fail = false) {
MySQLSession client;
if (should_fail) {
EXPECT_THROW_LIKE(client.connect("127.0.0.1", router_port, "username",
"password", "", ""),
std::exception, "Error connecting to MySQL server");
out_port = "";
return;
} else {
ASSERT_NO_THROW(client.connect("127.0.0.1", router_port, "username",
"password", "", ""));
}
std::unique_ptr<MySQLSession::ResultRow> result{
client.query_one("select @@port")};
ASSERT_NE(nullptr, result.get());
ASSERT_EQ(1u, result->size());
out_port = std::string((*result)[0]);
}
ProcessWrapper &launch_cluster_node(unsigned cluster_port,
const std::string &data_dir) {
const std::string js_file = Path(data_dir).join("my_port.js").str();
auto &cluster_node = ProcessManager::launch_mysql_server_mock(
js_file, cluster_port, EXIT_SUCCESS, false);
return cluster_node;
}
ProcessWrapper &launch_standalone_server(unsigned server_port,
const std::string &data_dir) {
// it' does the same thing, just an alias for less confusion
return launch_cluster_node(server_port, data_dir);
}
ProcessWrapper &launch_router_static(const std::string &conf_dir,
const std::string &routing_section,
bool expect_error = false,
bool log_to_console = true) {
auto def_section = get_DEFAULT_defaults();
if (log_to_console) {
def_section["logging_folder"] = "";
}
// launch the router with the static routing configuration
const std::string conf_file =
create_config_file(conf_dir, routing_section, &def_section);
const int expected_exit_code = expect_error ? EXIT_FAILURE : EXIT_SUCCESS;
auto &router =
ProcessManager::launch_router({"-c", conf_file}, expected_exit_code);
return router;
}
ProcessWrapper &launch_router(const std::string &temp_test_dir,
const std::string &metadata_cache_section,
const std::string &routing_section) {
auto default_section = get_DEFAULT_defaults();
init_keyring(default_section, temp_test_dir);
// launch the router with metadata-cache configuration
const std::string conf_file = create_config_file(
temp_test_dir, metadata_cache_section + routing_section,
&default_section);
auto &router = ProcessManager::launch_router({"-c", conf_file},
EXIT_SUCCESS, true, false);
return router;
}
void kill_server(ProcessWrapper *server) {
EXPECT_NO_THROW(server->kill()) << server->get_full_output();
}
TcpPortPool port_pool_;
unsigned wait_for_cache_ready_timeout{1000};
unsigned wait_for_static_ready_timeout{100};
unsigned wait_for_process_exit_timeout{10000};
};
struct MetadataCacheTestParams {
std::string role;
std::string routing_strategy;
std::string mode;
// consecutive nodes ids that we expect to be connected to
std::vector<unsigned> expected_node_connections;
bool round_robin;
MetadataCacheTestParams(const std::string &role_,
const std::string &routing_strategy_,
const std::string &mode_,
std::vector<unsigned> expected_node_connections_,
bool round_robin_ = false)
: role(role_),
routing_strategy(routing_strategy_),
mode(mode_),
expected_node_connections(expected_node_connections_),
round_robin(round_robin_) {}
};
::std::ostream &operator<<(::std::ostream &os,
const MetadataCacheTestParams &mcp) {
return os << "role=" << mcp.role
<< ", routing_strtegy=" << mcp.routing_strategy
<< ", mode=" << mcp.mode;
}
class RouterRoutingStrategyMetadataCache
: public RouterRoutingStrategyTest,
public ::testing::WithParamInterface<MetadataCacheTestParams> {
protected:
virtual void SetUp() { RouterRoutingStrategyTest::SetUp(); }
};
////////////////////////////////////////
/// MATADATA-CACHE ROUTING TESTS
////////////////////////////////////////
TEST_P(RouterRoutingStrategyMetadataCache, MetadataCacheRoutingStrategy) {
auto test_params = GetParam();
TempDirectory temp_test_dir;
const std::vector<uint16_t> cluster_nodes_ports{
port_pool_.get_next_available(), // first is PRIMARY
port_pool_.get_next_available(), port_pool_.get_next_available(),
port_pool_.get_next_available()};
const std::vector<uint16_t> cluster_nodes_http_ports{
port_pool_.get_next_available(), // first is PRIMARY
port_pool_.get_next_available(), port_pool_.get_next_available(),
port_pool_.get_next_available()};
std::vector<ProcessWrapper *> cluster_nodes;
// launch the primary node working also as metadata server
const auto json_file =
get_data_dir().join("metadata_3_secondaries_pass.js").str();
const auto http_port = cluster_nodes_http_ports[0];
auto &primary_node = launch_mysql_server_mock(
json_file, cluster_nodes_ports[0], EXIT_SUCCESS, false, http_port);
ASSERT_NO_FATAL_FAILURE(
check_port_ready(primary_node, cluster_nodes_ports[0]));
EXPECT_TRUE(MockServerRestClient(http_port).wait_for_rest_endpoint_ready());
set_mock_metadata(http_port, "", cluster_nodes_ports);
cluster_nodes.emplace_back(&primary_node);
// launch the router with metadata-cache configuration
const auto router_port = port_pool_.get_next_available();
const std::string metadata_cache_section =
get_metadata_cache_section(cluster_nodes_ports[0]);
const std::string routing_section = get_metadata_cache_routing_section(
router_port, test_params.role, test_params.routing_strategy,
test_params.mode);
const auto monitoring_port = port_pool_.get_next_available();
const std::string monitoring_section =
get_monitoring_section(monitoring_port, temp_test_dir.name());
auto &router = launch_router(temp_test_dir.name(),
metadata_cache_section + monitoring_section,
routing_section);
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port))
<< router.get_full_output();
// launch the secondary cluster nodes
for (unsigned port = 1; port < cluster_nodes_ports.size(); ++port) {
auto &secondary_node =
launch_cluster_node(cluster_nodes_ports[port], get_data_dir().str());
cluster_nodes.emplace_back(&secondary_node);
ASSERT_NO_FATAL_FAILURE(
check_port_ready(secondary_node, cluster_nodes_ports[port]));
}
// give the router a chance to initialise metadata-cache module
// there is currently now easy way to check that
SCOPED_TRACE("// waiting " + std::to_string(wait_for_cache_ready_timeout) +
"ms until metadata is initialized");
RestMetadataClient::MetadataStatus metadata_status;
RestMetadataClient rest_metadata_client("127.0.0.1", monitoring_port,
kRestApiUsername, kRestApiPassword);
ASSERT_NO_ERROR(rest_metadata_client.wait_for_cache_ready(
std::chrono::milliseconds(wait_for_cache_ready_timeout), metadata_status))
<< router.get_full_logfile();
if (!test_params.round_robin) {
// check if the server nodes are being used in the expected order
std::string node_port;
for (auto expected_node_id : test_params.expected_node_connections) {
ASSERT_NO_FATAL_FAILURE(
connect_client_and_query_port(router_port, node_port));
EXPECT_EQ(std::to_string(cluster_nodes_ports[expected_node_id]),
node_port);
}
} else {
// for round-robin we can't be sure which server will be the starting one
// on Solaris wait_for_port_ready() causes the router to switch to the next
// server while on other OSes it does not. We check it the round robin is
// done on provided set of ids.
const auto &expected_nodes = test_params.expected_node_connections;
std::string node_port;
size_t first_port_id{0};
for (size_t i = 0; i < expected_nodes.size() + 1;
++i) { // + 1 to check that after
// full round it starts from beginning
ASSERT_NO_FATAL_FAILURE(
connect_client_and_query_port(router_port, node_port));
if (i == 0) { // first-connection
const auto &real_port_iter =
std::find(cluster_nodes_ports.begin(), cluster_nodes_ports.end(),
static_cast<uint16_t>(std::atoi(node_port.c_str())));
ASSERT_NE(real_port_iter, cluster_nodes_ports.end());
auto port_id_ = real_port_iter - std::begin(cluster_nodes_ports);
EXPECT_TRUE(std::find(expected_nodes.begin(), expected_nodes.end(),
port_id_) != expected_nodes.end());
first_port_id =
std::find(expected_nodes.begin(), expected_nodes.end(), port_id_) -
expected_nodes.begin();
} else {
const auto current_idx = (first_port_id + i) % expected_nodes.size();
const auto expected_node_id = expected_nodes[current_idx];
EXPECT_EQ(std::to_string(cluster_nodes_ports[expected_node_id]),
node_port);
}
}
}
ASSERT_THAT(router.kill(), testing::Eq(0));
}
INSTANTIATE_TEST_CASE_P(
MetadataCacheRoutingStrategy, RouterRoutingStrategyMetadataCache,
// node_id=0 is PRIARY, node_id=1..3 are SECONDARY
::testing::Values(
// test round-robin on SECONDARY servers
// we expect 1->2->3->1 for 4 consecutive connections
MetadataCacheTestParams("SECONDARY", "round-robin", "", {1, 2, 3},
/*round-robin=*/true),
// test first-available on SECONDARY servers
// we expect 1->1->1 for 3 consecutive connections
MetadataCacheTestParams("SECONDARY", "first-available", "", {1, 1, 1}),
// *basic* test round-robin-with-fallback
// we expect 1->2->3->1 for 4 consecutive connections
// as there are SECONDARY servers available (PRIMARY id=0 should not be
// used)
MetadataCacheTestParams("SECONDARY", "round-robin-with-fallback", "",
{1, 2, 3},
/*round-robin=*/true),
// test round-robin on PRIMARY_AND_SECONDARY
// we expect the primary to participate in the round-robin from the
// beginning we expect 0->1->2->3->0 for 5 consecutive connections
MetadataCacheTestParams("PRIMARY_AND_SECONDARY", "round-robin", "",
{0, 1, 2, 3},
/*round-robin=*/true),
// test round-robin with allow-primary-reads=yes
// this should work similar to PRIMARY_AND_SECONDARY
// we expect 0->1->2->3->0 for 5 consecutive connections
MetadataCacheTestParams("SECONDARY&allow_primary_reads=yes", "",
"read-only", {0, 1, 2, 3},
/*round-robin=*/true),
// test first-available on PRIMARY
// we expect 0->0->0 for 2 consecutive connections
MetadataCacheTestParams("PRIMARY", "first-available", "", {0, 0}),
// test round-robin on PRIMARY
// there is single primary so we expect 0->0->0 for 2 consecutive
// connections
MetadataCacheTestParams("PRIMARY", "round-robin", "", {0, 0})));
////////////////////////////////////////
/// STATIC ROUTING TESTS
////////////////////////////////////////
class RouterRoutingStrategyTestRoundRobin
: public RouterRoutingStrategyTest,
// r. strategy, mode
public ::testing::WithParamInterface<
std::pair<std::string, std::string>> {
protected:
virtual void SetUp() { RouterRoutingStrategyTest::SetUp(); }
};
TEST_P(RouterRoutingStrategyTestRoundRobin, StaticRoutingStrategyRoundRobin) {
TempDirectory temp_test_dir;
TempDirectory conf_dir("conf");
const std::vector<uint16_t> server_ports{port_pool_.get_next_available(),
port_pool_.get_next_available(),
port_pool_.get_next_available()};
// launch the standalone servers
std::vector<ProcessWrapper *> server_instances;
for (auto &server_port : server_ports) {
auto &secondary_node =
launch_standalone_server(server_port, get_data_dir().str());
ASSERT_NO_FATAL_FAILURE(check_port_ready(secondary_node, server_port));
server_instances.emplace_back(&secondary_node);
}
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const auto routing_strategy = GetParam().first;
const auto mode = GetParam().second;
const std::string routing_section = get_static_routing_section(
router_port, server_ports, routing_strategy, mode);
auto &router = launch_router_static(conf_dir.name(), routing_section);
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
std::this_thread::sleep_for(
std::chrono::milliseconds(wait_for_static_ready_timeout));
// expect consecutive connections to be done in round-robin fashion
// will start with the second because wait_for_port_ready on the router will
// cause it to switch
std::string node_port;
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[1]), node_port);
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[2]), node_port);
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[0]), node_port);
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[1]), node_port);
}
// We expect round robin for routing-strategy=round-robin and as default for
// read-only
INSTANTIATE_TEST_CASE_P(
StaticRoutingStrategyRoundRobin, RouterRoutingStrategyTestRoundRobin,
::testing::Values(
std::make_pair(std::string("round-robin"), std::string("")),
std::make_pair(std::string("round-robin"), std::string("read-only")),
std::make_pair(std::string("round-robin"), std::string("read-write")),
std::make_pair(std::string(""), std::string("read-only"))));
class RouterRoutingStrategyTestFirstAvailable
: public RouterRoutingStrategyTest,
// r. strategy, mode
public ::testing::WithParamInterface<
std::pair<std::string, std::string>> {
protected:
virtual void SetUp() { RouterRoutingStrategyTest::SetUp(); }
};
TEST_P(RouterRoutingStrategyTestFirstAvailable,
StaticRoutingStrategyFirstAvailable) {
TempDirectory temp_test_dir;
TempDirectory conf_dir("conf");
const std::vector<uint16_t> server_ports{port_pool_.get_next_available(),
port_pool_.get_next_available(),
port_pool_.get_next_available()};
// launch the standalone servers
std::vector<ProcessWrapper *> server_instances;
for (auto &server_port : server_ports) {
auto &secondary_node =
launch_standalone_server(server_port, get_data_dir().str());
ASSERT_NO_FATAL_FAILURE(check_port_ready(secondary_node, server_port));
server_instances.emplace_back(&secondary_node);
}
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const auto routing_strategy = GetParam().first;
const auto mode = GetParam().second;
const std::string routing_section = get_static_routing_section(
router_port, server_ports, routing_strategy, mode);
auto &router = launch_router_static(conf_dir.name(), routing_section);
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// expect consecutive connections to be done in first-available fashion
std::string node_port;
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[0]), node_port);
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[0]), node_port);
// "kill" server 1 and 2, expect moving to server 3
kill_server(server_instances[0]);
kill_server(server_instances[1]);
// now we should connect to 3rd server
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[2]), node_port);
// kill also 3rd server
kill_server(server_instances[2]);
// expect connection failure
connect_client_and_query_port(router_port, node_port, /*should_fail=*/true);
EXPECT_EQ("", node_port);
// bring back 1st server
server_instances.emplace_back(
&launch_standalone_server(server_ports[0], get_data_dir().str()));
ASSERT_NO_FATAL_FAILURE(check_port_ready(
*server_instances[server_instances.size() - 1], server_ports[0]));
// we should now succesfully connect to this server
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[0]), node_port);
}
// We expect first-available for routing-strategy=first-available and as default
// for read-write
INSTANTIATE_TEST_CASE_P(
StaticRoutingStrategyFirstAvailable,
RouterRoutingStrategyTestFirstAvailable,
::testing::Values(
std::make_pair(std::string("first-available"), std::string("")),
std::make_pair(std::string("first-available"),
std::string("read-write")),
std::make_pair(std::string("first-available"),
std::string("read-only")),
std::make_pair(std::string(""), std::string("read-write"))));
// for non-param tests
class RouterRoutingStrategyStatic : public RouterRoutingStrategyTest {
protected:
virtual void SetUp() { RouterRoutingStrategyTest::SetUp(); }
};
TEST_F(RouterRoutingStrategyStatic, StaticRoutingStrategyNextAvailable) {
TempDirectory temp_test_dir;
TempDirectory conf_dir("conf");
const std::vector<uint16_t> server_ports{port_pool_.get_next_available(),
port_pool_.get_next_available(),
port_pool_.get_next_available()};
// launch the standalone servers
std::vector<ProcessWrapper *> server_instances;
for (auto &server_port : server_ports) {
auto &secondary_node =
launch_standalone_server(server_port, get_data_dir().str());
ASSERT_NO_FATAL_FAILURE(check_port_ready(secondary_node, server_port));
server_instances.emplace_back(&secondary_node);
}
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const std::string routing_section =
get_static_routing_section(router_port, server_ports, "next-available");
auto &router = launch_router_static(conf_dir.name(), routing_section);
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// expect consecutive connections to be done in first-available fashion
std::string node_port;
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[0]), node_port);
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[0]), node_port);
// "kill" server 1 and 2, expect connection to server 3 after that
kill_server(server_instances[0]);
kill_server(server_instances[1]);
// now we should connect to 3rd server
connect_client_and_query_port(router_port, node_port);
EXPECT_EQ(std::to_string(server_ports[2]), node_port);
// kill also 3rd server
kill_server(server_instances[2]);
// expect connection failure
connect_client_and_query_port(router_port, node_port, /*should_fail=*/true);
EXPECT_EQ("", node_port);
// bring back 1st server
server_instances.emplace_back(
&launch_standalone_server(server_ports[0], get_data_dir().str()));
ASSERT_NO_FATAL_FAILURE(check_port_ready(
*server_instances[server_instances.size() - 1], server_ports[0]));
// we should NOT connect to this server (in next-available we NEVER go back)
connect_client_and_query_port(router_port, node_port, /*should_fail=*/true);
EXPECT_EQ("", node_port);
}
// configuration error scenarios
TEST_F(RouterRoutingStrategyStatic, InvalidStrategyName) {
TempDirectory temp_test_dir;
TempDirectory conf_dir("conf");
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const std::string routing_section = get_static_routing_section_error(
router_port, {1, 2}, "round-robin-with-fallback", "read-only");
auto &router = launch_router_static(conf_dir.name(), routing_section,
/*expect_error=*/true);
check_exit_code(router, EXIT_FAILURE);
EXPECT_TRUE(
router.expect_output("Configuration error: option routing_strategy in "
"[routing:test_default] is invalid; "
"valid are first-available, next-available, and "
"round-robin (was 'round-robin-with-fallback'"))
<< router.get_full_logfile();
}
TEST_F(RouterRoutingStrategyStatic, InvalidMode) {
TempDirectory conf_dir("conf");
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const std::string routing_section = get_static_routing_section_error(
router_port, {1, 2}, "invalid", "read-only");
auto &router = launch_router_static(conf_dir.name(), routing_section,
/*expect_error=*/true);
check_exit_code(router, EXIT_FAILURE);
EXPECT_TRUE(router.expect_output(
"option routing_strategy in [routing:test_default] is invalid; valid are "
"first-available, next-available, and round-robin (was 'invalid')"))
<< router.get_full_logfile();
}
TEST_F(RouterRoutingStrategyStatic, BothStrategyAndModeMissing) {
TempDirectory conf_dir("conf");
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const std::string routing_section =
get_static_routing_section(router_port, {1, 2}, "");
auto &router = launch_router_static(conf_dir.name(), routing_section,
/*expect_error=*/true);
check_exit_code(router, EXIT_FAILURE);
EXPECT_TRUE(
router.expect_output("Configuration error: option routing_strategy in "
"[routing:test_default] is required"))
<< router.get_full_logfile();
}
TEST_F(RouterRoutingStrategyStatic, RoutingSrtategyEmptyValue) {
TempDirectory conf_dir("conf");
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const std::string routing_section =
get_static_routing_section_error(router_port, {1, 2}, "", "read-only");
auto &router = launch_router_static(conf_dir.name(), routing_section,
/*expect_error=*/true);
check_exit_code(router, EXIT_FAILURE);
EXPECT_TRUE(
router.expect_output("Configuration error: option routing_strategy in "
"[routing:test_default] needs a value"))
<< router.get_full_logfile();
}
TEST_F(RouterRoutingStrategyStatic, ModeEmptyValue) {
TempDirectory conf_dir("conf");
// launch the router with the static configuration
const auto router_port = port_pool_.get_next_available();
const std::string routing_section = get_static_routing_section_error(
router_port, {1, 2}, "first-available", "");
auto &router = launch_router_static(conf_dir.name(), routing_section,
/*expect_error=*/true);
check_exit_code(router, EXIT_FAILURE);
EXPECT_TRUE(
router.expect_output("Configuration error: option mode in "
"[routing:test_default] needs a value"))
<< router.get_full_logfile();
}
int main(int argc, char *argv[]) {
init_windows_sockets();
ProcessManager::set_origin(Path(argv[0]).dirname());
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}