1332 lines
44 KiB
C++
1332 lines
44 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 <fstream>
|
|
#include <string>
|
|
|
|
#include "dim.h"
|
|
#include "filesystem_utils.h"
|
|
#include "gmock/gmock.h"
|
|
#include "keyring/keyring_manager.h"
|
|
#include "mock_server_rest_client.h"
|
|
#include "mock_server_testutils.h"
|
|
#include "random_generator.h"
|
|
#include "router_component_test.h"
|
|
#include "script_generator.h"
|
|
#include "socket_operations.h"
|
|
#include "tcp_port_pool.h"
|
|
#include "utils.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
/**
|
|
* @file
|
|
* @brief Component Tests for the bootstrap operation
|
|
*/
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
// we create a number of classes to logically group tests together. But to avoid
|
|
// code duplication, we derive them from a class which contains the common code
|
|
// they need.
|
|
class CommonBootstrapTest : public RouterComponentTest {
|
|
protected:
|
|
static void SetUpTestCase() { my_hostname = "dont.query.dns"; }
|
|
|
|
TcpPortPool port_pool_;
|
|
TempDirectory bootstrap_dir;
|
|
TempDirectory tmp_dir;
|
|
static std::string my_hostname;
|
|
|
|
struct Config {
|
|
std::string ip;
|
|
unsigned int port;
|
|
uint16_t http_port;
|
|
std::string js_filename;
|
|
};
|
|
|
|
void bootstrap_failover(
|
|
const std::vector<Config> &servers,
|
|
const std::vector<std::string> &router_options = {},
|
|
int expected_exitcode = 0,
|
|
const std::vector<std::string> &expected_output_regex = {},
|
|
std::chrono::milliseconds wait_for_exit_timeout = 10000ms);
|
|
|
|
friend std::ostream &operator<<(
|
|
std::ostream &os,
|
|
const std::vector<std::tuple<ProcessWrapper &, unsigned int>> &T);
|
|
};
|
|
|
|
std::string CommonBootstrapTest::my_hostname;
|
|
|
|
std::ostream &operator<<(
|
|
std::ostream &os,
|
|
const std::vector<std::tuple<ProcessWrapper &, unsigned int>> &T) {
|
|
for (auto &t : T) {
|
|
auto &proc = std::get<0>(t);
|
|
|
|
os << "member@" << std::to_string(std::get<1>(t)) << ": "
|
|
<< proc.get_current_output() << std::endl;
|
|
}
|
|
return os;
|
|
}
|
|
|
|
/**
|
|
* the tiny power function that does all the work.
|
|
*
|
|
* - build environment
|
|
* - start mock servers based on Config[]
|
|
* - pass router_options to the launched router
|
|
* - check the router exits as expected
|
|
* - check output of router contains the expected lines
|
|
*/
|
|
void CommonBootstrapTest::bootstrap_failover(
|
|
const std::vector<Config> &mock_server_configs,
|
|
const std::vector<std::string> &router_options, int expected_exitcode,
|
|
const std::vector<std::string> &expected_output_regex,
|
|
std::chrono::milliseconds wait_for_exit_timeout) {
|
|
std::string cluster_name("mycluster");
|
|
|
|
std::vector<std::pair<std::string, unsigned>> gr_members;
|
|
for (const auto &mock_server_config : mock_server_configs) {
|
|
gr_members.emplace_back(mock_server_config.ip, mock_server_config.port);
|
|
}
|
|
|
|
std::vector<std::tuple<ProcessWrapper &, unsigned int>> mock_servers;
|
|
|
|
// start the mocks
|
|
for (const auto &mock_server_config : mock_server_configs) {
|
|
if (mock_server_config.js_filename.empty()) continue;
|
|
|
|
const auto port = mock_server_config.port;
|
|
const auto http_port = mock_server_config.http_port;
|
|
mock_servers.emplace_back(
|
|
launch_mysql_server_mock(mock_server_config.js_filename, port,
|
|
EXIT_SUCCESS, false, http_port),
|
|
port);
|
|
|
|
ProcessWrapper &mock_server = std::get<0>(mock_servers.back());
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
check_port_ready(mock_server, static_cast<uint16_t>(port)));
|
|
|
|
EXPECT_TRUE(MockServerRestClient(http_port).wait_for_rest_endpoint_ready());
|
|
set_mock_bootstrap_data(http_port, cluster_name, gr_members);
|
|
}
|
|
|
|
std::vector<std::string> router_cmdline;
|
|
|
|
if (router_options.size()) {
|
|
router_cmdline = router_options;
|
|
} else {
|
|
router_cmdline.emplace_back("--bootstrap=" + gr_members[0].first + ":" +
|
|
std::to_string(gr_members[0].second));
|
|
|
|
router_cmdline.emplace_back("--report-host");
|
|
router_cmdline.emplace_back(my_hostname);
|
|
router_cmdline.emplace_back("-d");
|
|
router_cmdline.emplace_back(bootstrap_dir.name());
|
|
}
|
|
|
|
// launch the router
|
|
auto &router = launch_router(router_cmdline, expected_exitcode);
|
|
|
|
// type in the password
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
check_exit_code(router, expected_exitcode, wait_for_exit_timeout))
|
|
<< std::get<0>(mock_servers[0]).get_full_output();
|
|
|
|
// split the output into lines
|
|
std::vector<std::string> lines;
|
|
{
|
|
std::istringstream ss{router.get_full_output()};
|
|
|
|
for (std::string line; std::getline(ss, line);) {
|
|
lines.emplace_back(line);
|
|
}
|
|
}
|
|
|
|
for (auto const &re_str : expected_output_regex) {
|
|
EXPECT_THAT(lines, ::testing::Contains(::testing::ContainsRegex(re_str)))
|
|
<< "router:" << router.get_full_output() << std::endl
|
|
<< mock_servers;
|
|
}
|
|
|
|
if (EXIT_SUCCESS == expected_exitcode) {
|
|
// fetch all the content for debugging
|
|
for (auto &mock_server : mock_servers) {
|
|
std::get<0>(mock_server).get_full_output();
|
|
}
|
|
EXPECT_THAT(lines,
|
|
::testing::Contains(
|
|
"# MySQL Router configured for the InnoDB cluster '" +
|
|
cluster_name + "'"))
|
|
<< "router:" << router.get_full_output() << std::endl
|
|
<< mock_servers;
|
|
|
|
// check the output configuration file:
|
|
// 1. check if the valid default ttl has been put in the configuraion:
|
|
EXPECT_TRUE(find_in_file(
|
|
bootstrap_dir.name() + "/mysqlrouter.conf",
|
|
[](const std::string &line) -> bool { return line == "ttl=0.5"; },
|
|
std::chrono::milliseconds(0)));
|
|
// 2. check that bootstrap server addresses is no longer in cofiguration
|
|
// file (it has been replaced with dynamic_config)
|
|
const std::string conf_file = bootstrap_dir.name() + "/mysqlrouter.conf";
|
|
EXPECT_FALSE(find_in_file(
|
|
conf_file,
|
|
[](const std::string &line) -> bool {
|
|
return line.find("bootstrap_server_addresses") != std::string::npos;
|
|
},
|
|
std::chrono::milliseconds(0)))
|
|
<< get_file_output("mysqlrouter.conf", bootstrap_dir.name());
|
|
// 3. check that the config files (static and dynamic) have the proper
|
|
// access rights
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
check_config_file_access_rights(conf_file, /*read_only=*/true));
|
|
const std::string state_file = bootstrap_dir.name() + "/data/state.json";
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
check_config_file_access_rights(state_file, /*read_only=*/false));
|
|
}
|
|
}
|
|
|
|
class RouterBootstrapTest : public CommonBootstrapTest {};
|
|
|
|
/**
|
|
* @test
|
|
* verify that the router's \c --bootstrap can bootstrap
|
|
* from metadata-servers's PRIMARY over TCP/IP
|
|
* @test
|
|
* Group Replication roles:
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
* - SECONDARY (not used)
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapOk) {
|
|
std::vector<Config> config{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap.js").str()},
|
|
};
|
|
|
|
bootstrap_failover(config);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
/**
|
|
* verify that the router's \c --user is ignored if it matches the current
|
|
* username.
|
|
*
|
|
* skipped on win32 as \c --user isn't supported on windows
|
|
*
|
|
* @test
|
|
* test if Bug#27698052 is fixed
|
|
* @test
|
|
* Group Replication roles:
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
* - SECONDARY (not used)
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapUserIsCurrentUser) {
|
|
auto current_userid = geteuid();
|
|
auto current_userpw = getpwuid(current_userid);
|
|
if (current_userpw != nullptr) {
|
|
const char *current_username = current_userpw->pw_name;
|
|
|
|
std::vector<Config> mock_servers{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap.js").str()},
|
|
};
|
|
|
|
std::vector<std::string> router_options = {
|
|
"--bootstrap=" + mock_servers.at(0).ip + ":" +
|
|
std::to_string(mock_servers.at(0).port),
|
|
"-d",
|
|
bootstrap_dir.name(),
|
|
"--report-host",
|
|
my_hostname,
|
|
"--user",
|
|
current_username};
|
|
|
|
bootstrap_failover(mock_servers, router_options);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @test
|
|
* verify that the router's \c --bootstrap can bootstrap
|
|
* from metadata-server's PRIMARY over TCP/IP and generate
|
|
* a configuration with unix-sockets only
|
|
* @test
|
|
* Group Replication roles:
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
* - SECONDARY (not used)
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapOnlySockets) {
|
|
std::vector<Config> mock_servers{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap.js").str()},
|
|
};
|
|
|
|
std::vector<std::string> router_options = {
|
|
"--bootstrap=" + mock_servers.at(0).ip + ":" +
|
|
std::to_string(mock_servers.at(0).port),
|
|
"-d",
|
|
bootstrap_dir.name(),
|
|
"--report-host",
|
|
my_hostname,
|
|
"--conf-skip-tcp",
|
|
"--conf-use-sockets"};
|
|
|
|
bootstrap_failover(mock_servers, router_options,
|
|
#ifndef _WIN32
|
|
EXIT_SUCCESS,
|
|
{
|
|
"- Read/Write Connections: .*/mysqlx.sock",
|
|
"- Read/Only Connections: .*/mysqlxro.sock"
|
|
}
|
|
#else
|
|
1, { "Error: unknown option '--conf-skip-tcp'" }
|
|
#endif
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that the router's \c --bootstrap detects a unsupported
|
|
* metadata schema version
|
|
* @test
|
|
* Group Replication roles:
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
* - SECONDARY (not used)
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapUnsupportedSchemaVersion) {
|
|
std::vector<Config> mock_servers{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_unsupported_schema_version.js").str()},
|
|
};
|
|
|
|
// check that it failed as expected
|
|
bootstrap_failover(mock_servers, {}, EXIT_FAILURE,
|
|
{"^Error: This version of MySQL Router is not compatible "
|
|
"with the provided MySQL InnoDB cluster metadata"});
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstrap will fail-over to another node if the initial
|
|
* node is not writable
|
|
* @test
|
|
* Group Replication roles:
|
|
* - SECONDARY
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapFailoverSuperReadonly) {
|
|
std::vector<Config> config{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_super_read_only_1.js").str()},
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_super_read_only_2.js").str()},
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(), ""},
|
|
};
|
|
|
|
bootstrap_failover(config);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstrap will fail-over to another node if the initial
|
|
* node is not writable and 2nd candidate has connection problems
|
|
* @test
|
|
* Group Replication roles:
|
|
* - SECONDARY
|
|
* - <connect-failure>
|
|
* - PRIMARY
|
|
* @test
|
|
* connection problems could be anything from 'auth-failure' to
|
|
* 'network-errors'. This test uses a \c port==0 to create a failure which is
|
|
* reserved and unassigned.
|
|
*
|
|
* @note The implementation uses \c port=65536 to circumvents libmysqlclients
|
|
* \code{.py} if port == 0: port = 3306 \endcode default port assignment. As the
|
|
* port will later be narrowed to an 16bit unsigned integer \code port & 0xffff
|
|
* \endcode the code will connect to port 0 in the end.
|
|
*
|
|
* @todo As soon as the mysql-server-mock supports authentication failures
|
|
* the code can take that into account too.
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapFailoverSuperReadonly2ndNodeDead) {
|
|
std::vector<Config> config{
|
|
// member-1, PRIMARY, fails at first write
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_super_read_only_1.js").str()},
|
|
// member-2, unreachable
|
|
{"127.0.0.1", 65536, // 65536 % 0xffff = 0 (port 0), but we bypass
|
|
// libmysqlclient's default-port assignment
|
|
port_pool_.get_next_available(), ""},
|
|
// member-3, succeeds
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_super_read_only_2.js").str()},
|
|
};
|
|
|
|
bootstrap_failover(
|
|
config, {}, EXIT_SUCCESS,
|
|
{
|
|
"^Fetching Group Replication Members",
|
|
"^Failed connecting to 127\\.0\\.0\\.1:65536: .*, trying next$",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstrap fails over and continues if create-account fails
|
|
* due to 1st node not being writable
|
|
* @test
|
|
* Group Replication roles:
|
|
* - SECONDARY
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapFailoverSuperReadonlyCreateAccountFails) {
|
|
std::vector<Config> config{
|
|
// member-1: SECONDARY, fails at DROP USER due to RW request on RO node
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir()
|
|
.join("bootstrap_failover_super_read_only_dead_2nd_1.js")
|
|
.str()},
|
|
|
|
// member-2: PRIMARY, succeeds
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_reconfigure_ok.js").str()},
|
|
|
|
// member-3: defined, but unused
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(), ""},
|
|
};
|
|
|
|
bootstrap_failover(config);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstrap fails over and continues if
|
|
* create-account.drop-user fails due to 1st node not being writable
|
|
* @test
|
|
* Group Replication roles:
|
|
* - SECONDARY
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
*
|
|
*/
|
|
TEST_F(RouterBootstrapTest,
|
|
BootstrapFailoverSuperReadonlyCreateAccountDropUserFails) {
|
|
std::vector<Config> config{
|
|
// member-1: SECONDARY, fails on CREATE USER due to RW request on RO node
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir()
|
|
.join("bootstrap_failover_super_read_only_delete_user.js")
|
|
.str()},
|
|
|
|
// member-2: PRIMARY, succeeds
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir()
|
|
.join("bootstrap_failover_reconfigure_ok_3_old_users.js")
|
|
.str()},
|
|
|
|
// member-3: defined, but unused
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(), ""},
|
|
};
|
|
|
|
bootstrap_failover(config);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstrap fails over and continues if create-account.grant
|
|
* fails
|
|
* @test
|
|
* Group Replication roles:
|
|
* - SECONDARY
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
*
|
|
*/
|
|
TEST_F(RouterBootstrapTest,
|
|
BootstrapFailoverSuperReadonlyCreateAccountGrantFails) {
|
|
std::vector<Config> config{
|
|
// member-1: PRIMARY, fails after GRANT
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_at_grant.js").str()},
|
|
|
|
// member-2: PRIMARY, succeeds
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_reconfigure_ok.js").str()},
|
|
|
|
// member-3: defined, but unused
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(), ""},
|
|
};
|
|
|
|
bootstrap_failover(config);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstraping via a unix-socket fails over to the
|
|
* IP-addresses of the members
|
|
* @test
|
|
* Group Replication roles:
|
|
* - SECONDARY
|
|
* - PRIMARY
|
|
* - SECONDARY (not used)
|
|
* @test
|
|
* Initial connect via unix-socket to the 1st node, all further connects
|
|
* via TCP/IP
|
|
*
|
|
* @todo needs unix-socket support in the mock-server
|
|
*/
|
|
TEST_F(RouterBootstrapTest, DISABLED_BootstrapFailoverSuperReadonlyFromSocket) {
|
|
std::vector<Config> mock_servers{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_super_read_only_1.js").str()},
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_super_read_only_2.js").str()},
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(), ""},
|
|
};
|
|
|
|
std::vector<std::string> router_options = {
|
|
"--bootstrap=localhost", "--bootstrap-socket=" + mock_servers.at(0).ip,
|
|
"-d", bootstrap_dir.name()};
|
|
|
|
bootstrap_failover(mock_servers, router_options);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstrap fails over if PRIMARY crashes while bootstrapping
|
|
*
|
|
* @test
|
|
* Group Replication roles:
|
|
* - SECONDARY
|
|
* - PRIMARY (crashing)
|
|
* - PRIMARY
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapFailoverSuperReadonlyNewPrimaryCrash) {
|
|
std::vector<Config> mock_servers{
|
|
// member-1: PRIMARY, fails at DROP USER
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir()
|
|
.join("bootstrap_failover_super_read_only_dead_2nd_1.js")
|
|
.str()},
|
|
|
|
// member-2: PRIMARY, but crashing
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_at_crash.js").str()},
|
|
|
|
// member-3: newly elected PRIMARY, succeeds
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_failover_reconfigure_ok.js").str()},
|
|
};
|
|
|
|
bootstrap_failover(mock_servers);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify connection times at bootstrap can be configured
|
|
*/
|
|
TEST_F(RouterBootstrapTest,
|
|
BootstrapSucceedWhenServerResponseLessThanReadTimeout) {
|
|
std::vector<Config> mock_servers{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_exec_time_2_seconds.js").str()},
|
|
};
|
|
|
|
std::vector<std::string> router_options = {
|
|
"--bootstrap=" + mock_servers.at(0).ip + ":" +
|
|
std::to_string(mock_servers.at(0).port),
|
|
"--report-host",
|
|
my_hostname,
|
|
"-d",
|
|
bootstrap_dir.name(),
|
|
"--connect-timeout=3",
|
|
"--read-timeout=3"};
|
|
|
|
bootstrap_failover(mock_servers, router_options, EXIT_SUCCESS, {});
|
|
}
|
|
|
|
TEST_F(RouterBootstrapTest, BootstrapAccessErrorAtGrantStatement) {
|
|
std::vector<Config> config{
|
|
// member-1: PRIMARY, fails after GRANT
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_access_error_at_grant.js").str()},
|
|
|
|
// member-2: defined, but unused
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(), ""},
|
|
|
|
// member-3: defined, but unused
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(), ""},
|
|
};
|
|
|
|
bootstrap_failover(config, {}, EXIT_FAILURE,
|
|
{"Access denied for user 'native'@'%' to database "
|
|
"'mysql_innodb_cluster_metadata"});
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* ensure a resonable error message if schema exists, but no
|
|
* group-replication is setup.
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapNoGroupReplicationSetup) {
|
|
std::vector<Config> config{
|
|
// member-1: schema exists, but no group replication configured
|
|
{
|
|
"127.0.0.1",
|
|
port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_no_gr.js").str(),
|
|
},
|
|
};
|
|
|
|
bootstrap_failover(config, {}, EXIT_FAILURE,
|
|
{"to have Group Replication running"});
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* ensure a resonable error message if metadata schema does not exist.
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapNoMetadataSchema) {
|
|
std::vector<Config> config{
|
|
// member-1: no metadata schema
|
|
{
|
|
"127.0.0.1",
|
|
port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_no_schema.js").str(),
|
|
},
|
|
};
|
|
|
|
bootstrap_failover(config, {}, EXIT_FAILURE,
|
|
{"to contain the metadata of MySQL InnoDB Cluster"});
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify connection times at bootstrap can be configured
|
|
*/
|
|
TEST_F(RouterBootstrapTest, BootstrapFailWhenServerResponseExceedsReadTimeout) {
|
|
std::vector<Config> mock_servers{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap_exec_time_2_seconds.js").str()},
|
|
};
|
|
|
|
std::vector<std::string> router_options = {
|
|
"--bootstrap=" + mock_servers.at(0).ip + ":" +
|
|
std::to_string(mock_servers.at(0).port),
|
|
"-d", bootstrap_dir.name(), "--connect-timeout=1", "--read-timeout=1"};
|
|
|
|
bootstrap_failover(mock_servers, router_options, EXIT_FAILURE,
|
|
{"Error: Error executing MySQL query: Lost connection to "
|
|
"MySQL server during query \\(2013\\)"});
|
|
}
|
|
|
|
class RouterAccountHostTest : public CommonBootstrapTest {};
|
|
|
|
/**
|
|
* @test
|
|
* verify that --account-host:
|
|
* - works in general
|
|
* - can be applied multiple times in one go
|
|
* - can take '%' as a parameter
|
|
*/
|
|
TEST_F(RouterAccountHostTest, multiple_host_patterns) {
|
|
// to avoid duplication of tracefiles, we run the same test twice, with the
|
|
// only difference that 1st time we run --bootstrap before the --account-host,
|
|
// and second time we run it after
|
|
const auto server_port = port_pool_.get_next_available();
|
|
|
|
auto test_it = [&](const std::vector<std::string> &cmdline) -> void {
|
|
const std::string json_stmts =
|
|
get_data_dir()
|
|
.join("bootstrap_account_host_multiple_patterns.js")
|
|
.str();
|
|
|
|
// launch mock server and wait for it to start accepting connections
|
|
auto &server_mock =
|
|
launch_mysql_server_mock(json_stmts, server_port, EXIT_SUCCESS, false);
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router(cmdline);
|
|
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(router.expect_output(
|
|
"MySQL Router configured for the InnoDB cluster 'mycluster'", false,
|
|
5s))
|
|
<< "router: " << router.get_full_output() << std::endl
|
|
<< "server: " << server_mock.get_full_output();
|
|
|
|
check_exit_code(router, EXIT_SUCCESS);
|
|
|
|
server_mock.kill();
|
|
};
|
|
|
|
// NOTE: CREATE USER statements should run in unique(sort(hostname_list))
|
|
// fashion
|
|
|
|
// --bootstrap before --account-host
|
|
{
|
|
TempDirectory bootstrap_directory;
|
|
test_it({"--bootstrap=127.0.0.1:" + std::to_string(server_port),
|
|
"--report-host", my_hostname, "-d", bootstrap_directory.name(),
|
|
"--account-host", "host1", // 2nd CREATE USER
|
|
"--account-host", "%", // 1st CREATE USER
|
|
"--account-host", "host1", // \_ redundant, ignored
|
|
"--account-host", "host1", // /
|
|
"--account-host", "host3%"}); // 3rd CREATE USER
|
|
}
|
|
|
|
// --bootstrap after --account-host
|
|
{
|
|
TempDirectory bootstrap_directory;
|
|
test_it({"-d", bootstrap_directory.name(), "--report-host", my_hostname,
|
|
"--account-host", "host1", // 2nd CREATE USER
|
|
"--account-host", "%", // 1st CREATE USER
|
|
"--account-host", "host1", // \_ redundant, ignored
|
|
"--account-host", "host1", // /
|
|
"--account-host", "host3%", // 3rd CREATE USER
|
|
"--bootstrap=127.0.0.1:" + std::to_string(server_port)});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --account-host without required argument produces an error
|
|
* and exits
|
|
*/
|
|
TEST_F(RouterAccountHostTest, argument_missing) {
|
|
const unsigned server_port = port_pool_.get_next_available();
|
|
|
|
// launch the router in bootstrap mode
|
|
auto &router =
|
|
launch_router({"--bootstrap=127.0.0.1:" + std::to_string(server_port),
|
|
"--account-host"},
|
|
EXIT_FAILURE);
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(router.expect_output(
|
|
"option '--account-host' expects a value, got nothing"))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --account-host without --bootstrap switch produces an
|
|
* error and exits
|
|
*/
|
|
TEST_F(RouterAccountHostTest, without_bootstrap_flag) {
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router({"--account-host", "host1"}, EXIT_FAILURE);
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(router.expect_output(
|
|
"Option --account-host can only be used together with -B/--bootstrap"))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --account-host with illegal hostname argument correctly
|
|
* handles the error
|
|
*/
|
|
TEST_F(RouterAccountHostTest, illegal_hostname) {
|
|
const std::string json_stmts =
|
|
get_data_dir().join("bootstrap_account_host_pattern_too_long.js").str();
|
|
TempDirectory bootstrap_directory;
|
|
const auto server_port = port_pool_.get_next_available();
|
|
|
|
// launch mock server and wait for it to start accepting connections
|
|
auto &server_mock =
|
|
launch_mysql_server_mock(json_stmts, server_port, EXIT_SUCCESS, false);
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router(
|
|
{"--bootstrap=127.0.0.1:" + std::to_string(server_port), "--report-host",
|
|
my_hostname, "-d", bootstrap_directory.name(), "--account-host",
|
|
"veryveryveryveryveryveryveryveryveryveryveryveryveryveryverylonghost"},
|
|
1);
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(
|
|
router.expect_output("Error executing MySQL query: String "
|
|
"'veryveryveryveryveryveryveryveryveryveryveryveryve"
|
|
"ryveryverylonghost' is too long for host name"))
|
|
<< router.get_full_output() << std::endl
|
|
<< "server:\n"
|
|
<< server_mock.get_full_output();
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
class RouterReportHostTest : public CommonBootstrapTest {};
|
|
|
|
/**
|
|
* @test
|
|
* verify that --report-host works for the typical use case
|
|
*/
|
|
TEST_F(RouterReportHostTest, typical_usage) {
|
|
const auto server_port = port_pool_.get_next_available();
|
|
|
|
auto test_it = [&](const std::vector<std::string> &cmdline) -> void {
|
|
const std::string json_stmts =
|
|
get_data_dir().join("bootstrap_report_host.js").str();
|
|
|
|
// launch mock server and wait for it to start accepting connections
|
|
auto &server_mock =
|
|
launch_mysql_server_mock(json_stmts, server_port, EXIT_SUCCESS, false);
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router(cmdline);
|
|
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(
|
|
router.expect_output("MySQL Router configured for the "
|
|
"InnoDB cluster 'mycluster'"))
|
|
<< router.get_full_output() << std::endl
|
|
<< "server: " << server_mock.get_full_output();
|
|
check_exit_code(router, EXIT_SUCCESS);
|
|
|
|
server_mock.kill();
|
|
};
|
|
|
|
{
|
|
TempDirectory bootstrap_directory;
|
|
// --bootstrap before --report-host
|
|
test_it({"--bootstrap=127.0.0.1:" + std::to_string(server_port), "-d",
|
|
bootstrap_directory.name(), "--report-host", "host.foo.bar"});
|
|
}
|
|
|
|
{
|
|
TempDirectory bootstrap_directory;
|
|
// --bootstrap after --report-host
|
|
test_it({"-d", bootstrap_directory.name(), "--report-host", "host.foo.bar",
|
|
"--bootstrap=127.0.0.1:" + std::to_string(server_port)});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that multiple --report-host arguments produce an error
|
|
* and exit
|
|
*/
|
|
TEST_F(RouterReportHostTest, multiple_hostnames) {
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router({"--bootstrap=1.2.3.4:5678", "--report-host",
|
|
"host1", "--report-host", "host2"},
|
|
1);
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(
|
|
router.expect_output("Option --report-host can only be used once."))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --report-host without required argument produces an error
|
|
* and exits
|
|
*/
|
|
TEST_F(RouterReportHostTest, argument_missing) {
|
|
// launch the router in bootstrap mode
|
|
auto &router =
|
|
launch_router({"--bootstrap=1.2.3.4:5678", "--report-host"}, 1);
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(router.expect_output(
|
|
"option '--report-host' expects a value, got nothing"))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --report-host without --bootstrap switch produces an error
|
|
* and exits
|
|
*/
|
|
TEST_F(RouterReportHostTest, without_bootstrap_flag) {
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router({"--report-host", "host1"}, 1);
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(router.expect_output(
|
|
"Option --report-host can only be used together with -B/--bootstrap"))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --report-host with invalid hostname argument produces an
|
|
* error and exits
|
|
*
|
|
* @note
|
|
* There's a separate suite of unit tests which tests the validating code
|
|
* which determines if the hostname is valid or not - therefore here we
|
|
* only focus on how this invalid hostname will be handled - we don't
|
|
* concern outselves with correctness of hostname validation itself.
|
|
*/
|
|
TEST_F(RouterReportHostTest, invalid_hostname) {
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router(
|
|
{"--bootstrap", "1.2.3.4:5678", "--report-host", "^bad^hostname^"}, 1);
|
|
|
|
// check if the bootstraping was successful
|
|
EXPECT_TRUE(
|
|
router.expect_output("Error: Option --report-host has an invalid value."))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that bootstrap succeeds when master key writer is used
|
|
*
|
|
*/
|
|
TEST_F(RouterBootstrapTest,
|
|
NoMasterKeyFileWhenBootstrapPassWithMasterKeyReader) {
|
|
std::vector<Config> config{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap.js").str()},
|
|
};
|
|
|
|
ScriptGenerator script_generator(ProcessManager::get_origin(),
|
|
tmp_dir.name());
|
|
|
|
std::vector<std::string> router_options = {
|
|
"--bootstrap=" + config.at(0).ip + ":" +
|
|
std::to_string(config.at(0).port),
|
|
"--report-host",
|
|
my_hostname,
|
|
"-d",
|
|
bootstrap_dir.name(),
|
|
"--master-key-reader=" + script_generator.get_reader_script(),
|
|
"--master-key-writer=" + script_generator.get_writer_script()};
|
|
|
|
bootstrap_failover(config, router_options);
|
|
|
|
Path tmp(bootstrap_dir.name());
|
|
Path master_key_file(tmp.join("mysqlrouter.key").str());
|
|
ASSERT_FALSE(master_key_file.exists());
|
|
|
|
Path keyring_file(tmp.join("data").join("keyring").str());
|
|
ASSERT_TRUE(keyring_file.exists());
|
|
|
|
Path dir(tmp_dir.name());
|
|
Path data_file(dir.join("master_key").str());
|
|
ASSERT_TRUE(data_file.exists());
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that master key file is not overridden by sunsequent bootstrap.
|
|
*/
|
|
TEST_F(RouterBootstrapTest, MasterKeyFileNotChangedAfterSecondBootstrap) {
|
|
std::string master_key_path =
|
|
Path(bootstrap_dir.name()).join("master_key").str();
|
|
std::string keyring_path =
|
|
Path(bootstrap_dir.name()).join("data").join("keyring").str();
|
|
|
|
mysql_harness::mkdir(Path(bootstrap_dir.name()).str(), 0777);
|
|
mysql_harness::mkdir(Path(bootstrap_dir.name()).join("data").str(), 0777);
|
|
|
|
auto &proc = launch_command(get_origin().join("mysqlrouter_keyring").str(),
|
|
{
|
|
"init",
|
|
keyring_path,
|
|
"--master-key-file",
|
|
master_key_path,
|
|
});
|
|
ASSERT_NO_THROW(proc.wait_for_exit());
|
|
|
|
std::string master_key;
|
|
{
|
|
std::ifstream file(master_key_path);
|
|
std::stringstream iss;
|
|
iss << file.rdbuf();
|
|
master_key = iss.str();
|
|
}
|
|
|
|
std::vector<Config> mock_servers{
|
|
{"127.0.0.1", port_pool_.get_next_available(),
|
|
port_pool_.get_next_available(),
|
|
get_data_dir().join("bootstrap.js").str()},
|
|
};
|
|
|
|
std::vector<std::string> router_options = {
|
|
"--bootstrap=" + mock_servers.at(0).ip + ":" +
|
|
std::to_string(mock_servers.at(0).port),
|
|
"--report-host",
|
|
my_hostname,
|
|
"-d",
|
|
bootstrap_dir.name(),
|
|
"--force"};
|
|
|
|
bootstrap_failover(mock_servers, router_options, EXIT_SUCCESS, {});
|
|
{
|
|
std::ifstream file(master_key_path);
|
|
std::stringstream iss;
|
|
iss << file.rdbuf();
|
|
ASSERT_THAT(master_key, testing::Eq(iss.str()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that using --conf-use-gr-notifications creates proper config
|
|
* file entry.
|
|
*/
|
|
TEST_F(RouterBootstrapTest, ConfUseGrNotificationsYes) {
|
|
TempDirectory bootstrap_directory;
|
|
const auto server_port = port_pool_.get_next_available();
|
|
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
|
|
|
|
// launch mock server and wait for it to start accepting connections
|
|
auto &server_mock =
|
|
launch_mysql_server_mock(json_stmts, server_port, EXIT_SUCCESS, false);
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router(
|
|
{"--bootstrap=127.0.0.1:" + std::to_string(server_port), "-d",
|
|
bootstrap_directory.name(), "--conf-use-gr-notifications"});
|
|
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
check_exit_code(router, EXIT_SUCCESS);
|
|
|
|
// check if the valid config option was added to the file
|
|
EXPECT_TRUE(find_in_file(
|
|
bootstrap_directory.name() + "/mysqlrouter.conf",
|
|
[](const std::string &line) -> bool {
|
|
return line == "use_gr_notifications=1";
|
|
},
|
|
std::chrono::milliseconds(0)));
|
|
|
|
// check if valid TTL is set (with GR notifications it should be increased to
|
|
// 60s)
|
|
EXPECT_TRUE(find_in_file(
|
|
bootstrap_directory.name() + "/mysqlrouter.conf",
|
|
[](const std::string &line) -> bool { return line == "ttl=60"; },
|
|
std::chrono::milliseconds(0)));
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that NOT using --conf-use-gr-notifications
|
|
* creates a proper config file entry.
|
|
*/
|
|
TEST_F(RouterBootstrapTest, ConfUseGrNotificationsNo) {
|
|
TempDirectory bootstrap_directory;
|
|
const auto server_port = port_pool_.get_next_available();
|
|
|
|
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
|
|
|
|
// launch mock server and wait for it to start accepting connections
|
|
auto &server_mock =
|
|
launch_mysql_server_mock(json_stmts, server_port, EXIT_SUCCESS, false);
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
// launch the router in bootstrap mode
|
|
auto &router =
|
|
launch_router({"--bootstrap=127.0.0.1:" + std::to_string(server_port),
|
|
"-d", bootstrap_directory.name()});
|
|
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
check_exit_code(router, EXIT_SUCCESS);
|
|
|
|
// check if valid config option was added to the file
|
|
EXPECT_TRUE(find_in_file(
|
|
bootstrap_directory.name() + "/mysqlrouter.conf",
|
|
[](const std::string &line) -> bool {
|
|
return line == "use_gr_notifications=0";
|
|
},
|
|
std::chrono::milliseconds(0)));
|
|
|
|
// check if valid TTL is set (with no GR notifications it should be 0.5s)
|
|
EXPECT_TRUE(find_in_file(
|
|
bootstrap_directory.name() + "/mysqlrouter.conf",
|
|
[](const std::string &line) -> bool { return line == "ttl=0.5"; },
|
|
std::chrono::milliseconds(0)));
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --conf-use-gr-notifications used with no bootstrap
|
|
* causes proper error report
|
|
*/
|
|
TEST_F(RouterReportHostTest, ConfUseGrNotificationsNoBootstrap) {
|
|
auto &router = launch_router({"--conf-use-gr-notifications"}, 1);
|
|
|
|
EXPECT_TRUE(
|
|
router.expect_output("Error: Option --conf-use-gr-notifications can only "
|
|
"be used together with -B/--bootstrap"))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that --conf-use-gr-notifications used with some value
|
|
* causes proper error report
|
|
*/
|
|
TEST_F(RouterReportHostTest, ConfUseGrNotificationsHasValue) {
|
|
auto &router = launch_router(
|
|
{"-B", "somehost:12345", "--conf-use-gr-notifications=some"}, 1);
|
|
|
|
EXPECT_TRUE(
|
|
router.expect_output("Error: option '--conf-use-gr-notifications' does "
|
|
"not expect a value, but got a value"))
|
|
<< router.get_full_output() << std::endl;
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
class ErrorReportTest : public CommonBootstrapTest {};
|
|
|
|
/**
|
|
* @test
|
|
* verify that running bootstrap with -d with dir that already exists and
|
|
* is not empty gives an appropriate error to the user; particularly it
|
|
* should mention:
|
|
* - directory name
|
|
* - error type (it's not empty)
|
|
*/
|
|
TEST_F(ErrorReportTest, bootstrap_dir_exists_and_is_not_empty) {
|
|
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
|
|
const unsigned server_port = port_pool_.get_next_available();
|
|
|
|
TempDirectory bootstrap_directory;
|
|
|
|
// populate bootstrap dir with a file, so it's not empty
|
|
EXPECT_NO_THROW({
|
|
mysql_harness::Path path =
|
|
mysql_harness::Path(bootstrap_directory.name()).join("some_file");
|
|
std::ofstream of(path.str());
|
|
of << "blablabla";
|
|
});
|
|
|
|
// launch the router in bootstrap mode
|
|
auto &router = launch_router(
|
|
{
|
|
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
|
|
"--connect-timeout=1",
|
|
"--report-host",
|
|
my_hostname,
|
|
"-d",
|
|
bootstrap_directory.name(),
|
|
},
|
|
EXIT_FAILURE);
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
// verify that appropriate message was logged (first line) and error message
|
|
// printed (last line)
|
|
std::string err_msg = "Directory '" + bootstrap_directory.name() +
|
|
"' already contains files\n"
|
|
"Error: Directory already exits";
|
|
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
// unfortunately it's not (reasonably) possible to make folders read-only on
|
|
// Windows, therefore we can run the following tests only on Unix
|
|
// https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo
|
|
#ifndef _WIN32
|
|
/**
|
|
* @test
|
|
* verify that running bootstrap with -d with dir that already exists but
|
|
* is inaccessible gives an appropriate error to the user; particularly it
|
|
* should mention:
|
|
* - directory name
|
|
* - error type (permission denied)
|
|
* - suggests AppArmor config might be at fault
|
|
*/
|
|
TEST_F(ErrorReportTest, bootstrap_dir_exists_but_is_inaccessible) {
|
|
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
|
|
const unsigned server_port = port_pool_.get_next_available();
|
|
|
|
TempDirectory bootstrap_directory;
|
|
std::shared_ptr<void> exit_guard(nullptr, [&](void *) {
|
|
chmod(bootstrap_directory.name().c_str(),
|
|
S_IRUSR | S_IWUSR | S_IXUSR); // restore RWX for owner
|
|
});
|
|
|
|
// make bootstrap directory inaccessible to trigger the error
|
|
EXPECT_EQ(chmod(bootstrap_directory.name().c_str(), 0), 0);
|
|
|
|
// launch the router in bootstrap mode: -d set to existing but inaccessible
|
|
// dir
|
|
auto &router = launch_router(
|
|
{
|
|
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
|
|
"--connect-timeout=1",
|
|
"--report-host",
|
|
my_hostname,
|
|
"-d",
|
|
bootstrap_directory.name(),
|
|
},
|
|
EXIT_FAILURE);
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
// verify that appropriate message was logged (all but last) and error message
|
|
// printed (last line)
|
|
std::string err_msg =
|
|
"Failed to open directory '.*" + bootstrap_directory.name() +
|
|
"': Permission denied\n"
|
|
"This may be caused by insufficient rights or AppArmor settings.\n.*"
|
|
"Error: Could not check contents of existing deployment directory";
|
|
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
|
|
/**
|
|
* @test
|
|
* verify that running bootstrap with -d with dir that doesn't exists and
|
|
* cannot be created gives an appropriate error to the user; particularly
|
|
* it should mention:
|
|
* - directory name
|
|
* - error type (permission denied)
|
|
* - suggests AppArmor config might be at fault
|
|
*/
|
|
TEST_F(ErrorReportTest,
|
|
bootstrap_dir_does_not_exist_and_is_impossible_to_create) {
|
|
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
|
|
const unsigned server_port = port_pool_.get_next_available();
|
|
|
|
TempDirectory bootstrap_superdir;
|
|
std::shared_ptr<void> exit_guard(nullptr, [&](void *) {
|
|
chmod(bootstrap_superdir.name().c_str(),
|
|
S_IRUSR | S_IWUSR | S_IXUSR); // restore RWX for owner
|
|
});
|
|
|
|
// make bootstrap directory inaccessible to trigger the error
|
|
EXPECT_EQ(chmod(bootstrap_superdir.name().c_str(), 0), 0);
|
|
|
|
// launch the router in bootstrap mode: -d set to non-existent dir and
|
|
// impossible to create
|
|
std::string bootstrap_directory =
|
|
mysql_harness::Path(bootstrap_superdir.name()).join("subdir").str();
|
|
auto &router = launch_router(
|
|
{
|
|
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
|
|
"--connect-timeout=1",
|
|
"--report-host",
|
|
my_hostname,
|
|
"-d",
|
|
bootstrap_directory,
|
|
},
|
|
EXIT_FAILURE);
|
|
|
|
// add login hook
|
|
router.register_response("Please enter MySQL password for root: ",
|
|
"fake-pass\n");
|
|
|
|
// verify that appropriate message was logged (all but last) and error message
|
|
// printed (last line)
|
|
std::string err_msg =
|
|
"Cannot create directory '" + bootstrap_directory +
|
|
"': Permission denied\n"
|
|
"This may be caused by insufficient rights or AppArmor settings.\n.*"
|
|
"Error: Could not create deployment directory";
|
|
|
|
check_exit_code(router, EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char *argv[]) {
|
|
init_windows_sockets();
|
|
ProcessManager::set_origin(Path(argv[0]).dirname());
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|