unittest/modules/adminapi/mod_dba_common_t.cc (1,451 lines of code) (raw):
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
*
* 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 designed to work 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 either included with
* the program or referenced in the documentation.
*
* 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, version 2.0, 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 <string>
#include "modules/adminapi/common/accounts.h"
#include "modules/adminapi/common/common.h"
#include "modules/adminapi/common/group_replication_options.h"
#include "modules/adminapi/common/metadata_storage.h"
#include "modules/mod_shell.h"
#include "mysqlshdk/libs/db/mysql/session.h"
#include "mysqlshdk/libs/mysql/instance.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "scripting/types.h"
#include "unittest/gtest_clean.h"
#include "unittest/mysqlshdk/libs/mysql/user_privileges_t.h"
#include "unittest/test_utils/admin_api_test.h"
#include "unittest/test_utils/mocks/mysqlshdk/libs/db/mock_session.h"
#include "unittest/test_utils/mod_testutils.h"
#include "unittest/test_utils/shell_test_wrapper.h"
using mysqlshdk::mysql::Instance;
using mysqlshdk::mysql::Var_qualifier;
using mysqlshdk::utils::Version;
namespace testing {
class Dba_common_test : public tests::Admin_api_test {
public:
virtual void SetUp() {
Admin_api_test::SetUp();
reset_replayable_shell(
::testing::UnitTest::GetInstance()->current_test_info()->name());
}
virtual void TearDown() { Admin_api_test::TearDown(); }
protected:
static std::shared_ptr<mysqlsh::dba::Instance> create_session(
int port, std::string user = "root") {
auto connection_options = shcore::get_connection_options(
user + ":root@localhost:" + std::to_string(port), false);
return mysqlsh::dba::Instance::connect(connection_options);
}
std::shared_ptr<mysqlshdk::db::ISession> create_base_session(int port) {
auto session = mysqlshdk::db::mysql::Session::create();
mysqlshdk::db::Connection_options connection_options;
connection_options.set_host("localhost");
connection_options.set_port(port);
connection_options.set_user("user");
connection_options.set_password("");
session->connect(connection_options);
return session;
}
void disable_ssl_on_instance(int port, const std::string &unsecure_user) {
auto instance = create_session(port);
instance->query("create user " + unsecure_user +
"@'%' identified with "
"mysql_native_password by /*((*/ 'root' /*))*/");
testutil->stop_sandbox(port);
testutil->change_sandbox_conf(port, "ssl", "0", "mysqld");
testutil->change_sandbox_conf(port, "default_authentication_plugin",
"mysql_native_password", "mysqld");
testutil->start_sandbox(port);
}
};
TEST_F(Dba_common_test, resolve_cluster_ssl_mode_on_instance_with_ssl) {
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root");
auto instance = create_session(_mysql_sandbox_ports[0]);
mysqlsh::dba::Cluster_ssl_mode member_ssl_mode;
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "AUTO" ON
instance->set_sysvar("require_secure_transport", true, Var_qualifier::GLOBAL);
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("REQUIRED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=ON, "
"memberSslMode=AUTO");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "REQUIRED" ON
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::REQUIRED;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("REQUIRED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=ON, "
"memberSslMode=REQUIRED");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "VERIFY_CA" ON
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_CA;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_CA", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=ON, "
"memberSslMode=VERIFY_CA");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "VERIFY_IDENTITY" ON
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_IDENTITY;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_IDENTITY", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=ON, "
"memberSslMode=VERIFY_IDENTITY");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "AUTO" OFF
instance->set_sysvar("require_secure_transport", false,
Var_qualifier::GLOBAL);
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("REQUIRED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=OFF, "
"memberSslMode=AUTO");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "REQUIRED" OFF
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::REQUIRED;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("REQUIRED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=OFF, "
"memberSslMode=REQUIRED");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "VERIFY_CA" OFF
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_CA;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_CA", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=OFF, "
"memberSslMode=REQUIRED");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "VERIFY_IDENTITY" OFF
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_IDENTITY;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_IDENTITY", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=OFF, "
"memberSslMode=REQUIRED");
ADD_FAILURE();
}
// InstanceSSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// enabled "DISABLED" OFF
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::DISABLED;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("DISABLED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at require_secure_transport=OFF, "
"memberSslMode=DISABLED");
ADD_FAILURE();
}
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
}
TEST_F(Dba_common_test, resolve_cluster_ssl_mode_on_instance_without_ssl) {
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root");
disable_ssl_on_instance(_mysql_sandbox_ports[0], "unsecure");
mysqlsh::dba::Cluster_ssl_mode member_ssl_mode;
auto instance = create_session(_mysql_sandbox_ports[0], "unsecure");
// InstanceSSL memberSslMode
//----------- -------------
// disabled "REQUIRED"
// Tested in create_cluster_neg.js
// InstanceSSL memberSslMode
//----------- -------------
// disabled "AUTO"
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("DISABLED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at resolve_cluster_ssl_mode_010");
ADD_FAILURE();
}
// InstanceSSL memberSslMode
//----------- -------------
// disabled "DISABLED"
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::DISABLED;
mysqlsh::dba::resolve_ssl_mode_option("memberSslMode", "", *instance,
&member_ssl_mode);
EXPECT_STREQ("DISABLED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at resolve_cluster_ssl_mode_009");
ADD_FAILURE();
}
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
}
TEST_F(Dba_common_test, resolve_instance_ssl_cluster_with_ssl_required) {
shcore::Dictionary_t sandbox_opts = shcore::make_dict();
mysqlsh::dba::Cluster_ssl_mode member_ssl_mode;
(*sandbox_opts)["report_host"] = shcore::Value(hostname());
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root", sandbox_opts);
testutil->deploy_sandbox(_mysql_sandbox_ports[1], "root", sandbox_opts);
execute("shell.connect('root:root@localhost:" +
std::to_string(_mysql_sandbox_ports[0]) + "')");
testutil->expect_prompt(
"Should the configuration be changed accordingly? [y/N]: ", "y");
#ifdef HAVE_JS
execute(
"var c = dba.createCluster('sample', {memberSslMode:'REQUIRED', "
"gtidSetIsComplete: true})");
#else
execute(
"c = dba.create_cluster('sample', {'memberSslMode':'REQUIRED', "
"gtidSetIsComplete: true})");
#endif
execute("c.disconnect()");
execute("session.close()");
auto peer = create_session(_mysql_sandbox_ports[0]);
auto instance = create_session(_mysql_sandbox_ports[1]);
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED AUTO enabled
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode);
EXPECT_STREQ("REQUIRED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure with memberSslMode='AUTO', instance with "
"SSL");
ADD_FAILURE();
}
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED REQUIRED enabled
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::REQUIRED;
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode);
EXPECT_STREQ("REQUIRED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at memberSslMode='REQUIRED', instance "
"with SSL");
ADD_FAILURE();
}
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED VERIFY_CA enabled
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_CA;
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_CA", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at memberSslMode='VERIFY_CA', instance "
"with SSL");
ADD_FAILURE();
}
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED VERIFY_IDENTITY enabled
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_IDENTITY;
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_IDENTITY", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at memberSslMode='VERIFY_IDENTITY', instance "
"with SSL");
ADD_FAILURE();
}
// NOTE: This test only applies to 8.0. In 5.7, ssl_ca and ssl_capath are
// read-only so whenever the server is started with SSL support those are
// unchangeable. If the server is changed to not start with SSL support, then
// the command would fail with the error indicating the Cluster is using SSL
// so the instance cannot be added - Different code-path.
//
// To avoid looking at the version to decide whether the test shall be
// executed or not, we simply emplace the test in a try..catch block ensuring
// the exception caught when running in 5.7 is about ssl_ca being read-only.
try {
auto current_ssl_ca = instance->get_sysvar_string("ssl_ca").value_or("");
auto current_ssl_capath =
instance->get_sysvar_string("ssl_capath").value_or("");
instance->set_sysvar_default("ssl_ca");
instance->set_sysvar_default("ssl_capath");
// An exception should be raised when the CA options are not set and
// VERIFY_CA is used as memberSslMode
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_CA;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"memberSslMode 'VERIFY_CA' requires Certificate Authority (CA) "
"certificates to be supplied.");
MY_EXPECT_STDOUT_CONTAINS(
"ERROR: CA certificates options not set. ssl_ca or ssl_capath are "
"required, to supply a CA certificate that matches the one used by the "
"server.");
// An exception should be raised when the CA options are not set and
// VERIFY_IDENTITY is used as memberSslMode
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_IDENTITY;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"memberSslMode 'VERIFY_IDENTITY' requires Certificate Authority (CA) "
"certificates to be supplied.");
MY_EXPECT_STDOUT_CONTAINS(
"ERROR: CA certificates options not set. ssl_ca or ssl_capath are "
"required, to supply a CA certificate that matches the one used by the "
"server.");
// Set back the original values
instance->set_sysvar("ssl_ca", current_ssl_ca);
instance->set_sysvar("ssl_capath", current_ssl_capath);
} catch (const mysqlshdk::db::Error &e) {
// In 5.7 ssl_ca and ssl_capath are read_only variables
EXPECT_STREQ("Variable 'ssl_ca' is a read only variable", e.what());
}
// Cluster SSL memberSslMode
//----------- -------------
// REQUIRED DISABLED
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::DISABLED;
EXPECT_THROW_MSG(mysqlsh::dba::resolve_instance_ssl_mode_option(
*instance, *peer, &member_ssl_mode),
std::runtime_error,
"The cluster has TLS (encryption) enabled. "
"To add the instance '" +
instance->descr() +
"' to the cluster either disable TLS on the cluster, "
"remove the memberSslMode option or use it with any of "
"'AUTO', 'REQUIRED', 'VERIFY_CA' or "
"'VERIFY_IDENTITY'.");
// Cluster SSL memberSslMode Instance SSL
//----------- --------------- ------------
// VERIFY_CA "AUTO" enabled
try {
peer->execute("SET GLOBAL group_replication_ssl_mode=VERIFY_CA");
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_CA", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at memberSslMode='VERIFY_CA', instance "
"with SSL");
ADD_FAILURE();
}
// Cluster SSL memberSslMode Instance SSL
//----------- --------------- ------------
// VERIFY_CA "AUTO" enabled
try {
peer->execute("SET GLOBAL group_replication_ssl_mode=VERIFY_IDENTITY");
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode);
EXPECT_STREQ("VERIFY_IDENTITY", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE(
"Unexpected failure at memberSslMode='VERIFY_IDENTITY', instance "
"with SSL");
ADD_FAILURE();
}
peer->execute("SET GLOBAL group_replication_ssl_mode=REQUIRED");
disable_ssl_on_instance(_mysql_sandbox_ports[1], "unsecure");
instance = create_session(_mysql_sandbox_ports[1], "unsecure");
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED AUTO disabled
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
EXPECT_THROW_MSG(mysqlsh::dba::resolve_instance_ssl_mode_option(
*instance, *peer, &member_ssl_mode),
std::runtime_error,
"Instance '" + instance->descr() +
"' does not support TLS and cannot join a cluster with "
"TLS (encryption) enabled. Enable TLS support on the "
"instance and try again, otherwise it can only be added "
"to a cluster with TLS disabled.");
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED REQUIRED disabled
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::REQUIRED;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"Instance '" + instance->descr() +
"' does not support TLS and cannot join a cluster with TLS "
"(encryption) enabled. Enable TLS support on the instance and try "
"again, otherwise it can only be added to a cluster with TLS "
"disabled.");
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED VERIFY_CA disabled
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_CA;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"Instance '" + instance->descr() +
"' does not support TLS and cannot join a cluster with TLS "
"(encryption) enabled. Enable TLS support on the instance and try "
"again, otherwise it can only be added to a cluster with TLS "
"disabled.");
// Cluster SSL memberSslMode Instance SSL
//----------- ------------- ------------
// REQUIRED VERIFY_IDENTITY disabled
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::VERIFY_IDENTITY;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"Instance '" + instance->descr() +
"' does not support TLS and cannot join a cluster with TLS "
"(encryption) enabled. Enable TLS support on the instance and try "
"again, otherwise it can only be added to a cluster with TLS "
"disabled.");
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
testutil->destroy_sandbox(_mysql_sandbox_ports[1]);
}
TEST_F(Dba_common_test, resolve_instance_ssl_cluster_with_ssl_disabled) {
shcore::Dictionary_t sandbox_opts = shcore::make_dict();
(*sandbox_opts)["report_host"] = shcore::Value(hostname());
mysqlsh::dba::Cluster_ssl_mode member_ssl_mode;
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root", sandbox_opts);
testutil->deploy_sandbox(_mysql_sandbox_ports[1], "root", sandbox_opts);
execute("shell.connect('root:root@localhost:" +
std::to_string(_mysql_sandbox_ports[0]) + "')");
testutil->expect_prompt(
"Should the configuration be changed accordingly? [y/N]: ", "y");
#ifdef HAVE_JS
execute(
"var c = dba.createCluster('sample', {memberSslMode:'DISABLED', "
"gtidSetIsComplete: true})");
#else
execute(
"c = dba.create_cluster('sample', {'memberSslMode':'DISABLED', "
"gtidSetIsComplete: true})");
#endif
execute("c.disconnect()");
execute("session.close()");
auto peer = create_session(_mysql_sandbox_ports[0]);
auto instance = create_session(_mysql_sandbox_ports[1]);
// Cluster SSL memberSslMode
//----------- -------------
// DISABLED REQUIRED
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::REQUIRED;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"The cluster has TLS (encryption) disabled. "
"To add the instance '" +
instance->descr() +
"' to the cluster either enable TLS on the cluster, remove the "
"memberSslMode option or use it with any of 'AUTO' or 'DISABLED'.");
// Cluster SSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// DISABLED AUTO OFF
try {
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode);
EXPECT_STREQ("DISABLED", to_string(member_ssl_mode).c_str());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure using memberSslMode=AUTO");
ADD_FAILURE();
}
instance->set_sysvar("require_secure_transport", true, Var_qualifier::GLOBAL);
// Cluster SSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// DISABLED "" ON
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"The instance '" + instance->descr() +
"' is configured to require a secure transport but the cluster has "
"TLS disabled. To add the instance to the cluster, either turn OFF "
"the require_secure_transport option on the instance or enable TLS "
"on the cluster.");
// Cluster SSL memberSslMode require_secure_transport
//----------- ------------- ------------------------
// DISABLED AUTO ON
member_ssl_mode = mysqlsh::dba::Cluster_ssl_mode::AUTO;
EXPECT_THROW_MSG(
mysqlsh::dba::resolve_instance_ssl_mode_option(*instance, *peer,
&member_ssl_mode),
std::runtime_error,
"The instance '" + instance->descr() +
"' is configured to require a secure transport but the cluster has "
"TLS disabled. To add the instance to the cluster, either turn OFF "
"the require_secure_transport option on the instance or enable TLS "
"on the cluster.");
disable_ssl_on_instance(_mysql_sandbox_ports[1], "unsecure");
instance = create_session(_mysql_sandbox_ports[1], "unsecure");
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
testutil->destroy_sandbox(_mysql_sandbox_ports[1]);
}
TEST_F(Dba_common_test, check_admin_account_access_restrictions) {
using mysqlsh::dba::check_admin_account_access_restrictions;
using mysqlshdk::db::Type;
std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>();
mysqlshdk::mysql::Instance instance{mock_session};
// TEST: More than one account available for the user:
// - Return true independently of the interactive mode.
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'myhost'"}, {"'admin'@'otherhost'"}}}});
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "myhost", true,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'myhost'"}, {"'admin'@'otherhost'"}}}});
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "myhost", false,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
// TEST: Only one account not using wildcards (%) available for the user:
// - Interactive 'true': return false;
// - Interactive 'false': throw exception;
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"", {"grantee"}, {Type::String}, {{"'admin'@'myhost'"}}}});
EXPECT_FALSE(check_admin_account_access_restrictions(
instance, "admin", "myhost", true,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"", {"grantee"}, {Type::String}, {{"'admin'@'myhost'"}}}});
EXPECT_THROW_LIKE(check_admin_account_access_restrictions(
instance, "admin", "myhost", false,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION),
std::runtime_error,
"User 'admin' can only connect from 'myhost'.");
// TEST: Only one account with wildcard (%) available which is the same
// currently used (passed as parameter):
// - Return true independently of the interactive mode.
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"", {"grantee"}, {Type::String}, {{"'admin'@'%'"}}}});
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "%", true,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"", {"grantee"}, {Type::String}, {{"'admin'@'%'"}}}});
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "%", false,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
// TEST: Only one account with netmask which is the same
// currently used (passed as parameter):
// - Return true independently of the interactive mode.
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'192.168.1.0/255.255.255.0'"}}}});
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "192.168.1.20", true,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'192.168.1.0/255.255.255.0'"}}}});
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "192.168.1.20", false,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
// TEST: Only one account with IPv6 which is the same
// currently used (passed as parameter):
// - Interactive 'true': return false;
// - Interactive 'false': throw exception;
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'2001:0db8:85a3:0000:0000:8a2e:0370'"}}}});
EXPECT_FALSE(check_admin_account_access_restrictions(
instance, "admin", "2001:0db8:85a3:0000:0000:8a2e:0370", true,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'2001:0db8:85a3:0000:0000:8a2e:0370'"}}}});
EXPECT_THROW_LIKE(check_admin_account_access_restrictions(
instance, "admin", "2001:0db8:85a3:0000:0000:8a2e:0370",
false, mysqlsh::dba::Cluster_type::GROUP_REPLICATION),
std::runtime_error,
"User 'admin' can only connect from "
"'2001:0db8:85a3:0000:0000:8a2e:0370'.");
// TEST: Multiple accounts and one with wildcard (%) with the needed
// privileges, which is not the one currently used (passed as parameter):
// - Return true independently of the interactive mode.
auto expect_all_privileges =
[](const std::shared_ptr<Mock_session> &session) {
testing::user_privileges::Setup_options options;
options.user = "admin";
options.host = "%";
// Simulate version is always < 8.0.0 (5.7.0) to skip reading roles
// data.
options.version = Version(5, 7, 28);
options.grants = {
"GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, "
"SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW "
"DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, "
"REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, "
"CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, "
"CREATE TABLESPACE ON *.* TO 'admin'@'%' WITH GRANT OPTION",
};
testing::user_privileges::setup(options, session.get());
};
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'localhost'"}, {"'admin'@'%'"}}}});
expect_all_privileges(mock_session);
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "localhost", true,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
mock_session
->expect_query(
"SELECT DISTINCT grantee "
"FROM information_schema.user_privileges "
"WHERE grantee like '\\'admin\\'@%'")
.then_return({{"",
{"grantee"},
{Type::String},
{{"'admin'@'localhost'"}, {"'admin'@'%'"}}}});
expect_all_privileges(mock_session);
EXPECT_TRUE(check_admin_account_access_restrictions(
instance, "admin", "localhost", false,
mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
}
class Dba_common_cluster_functions : public Dba_common_test {
public:
static void SetUpTestCase() {
SetUpSampleCluster("Dba_common_cluster_functions/SetUpTestCase");
}
static void TearDownTestCase() {
TearDownSampleCluster("Dba_common_cluster_functions/TearDownTestCase");
}
};
// If the information on the Metadata and the GR group
// P_S info is the same get_newly_discovered_instances()
// result return an empty list
TEST_F(Dba_common_cluster_functions, get_newly_discovered_instances) {
auto md_instance = create_session(_mysql_sandbox_ports[0]);
std::shared_ptr<mysqlsh::dba::MetadataStorage> metadata;
metadata.reset(new mysqlsh::dba::MetadataStorage(md_instance));
try {
auto newly_discovered_instances_list(get_newly_discovered_instances(
*md_instance, metadata, _cluster->impl()->get_id()));
EXPECT_TRUE(newly_discovered_instances_list.empty());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at get_instances_md");
ADD_FAILURE();
}
}
// If the information on the Metadata and the GR group
// P_S info is the same get_unavailable_instances()
// should return an empty list
TEST_F(Dba_common_cluster_functions, get_unavailable_instances) {
auto md_instance = create_session(_mysql_sandbox_ports[0]);
std::shared_ptr<mysqlsh::dba::MetadataStorage> metadata;
metadata.reset(new mysqlsh::dba::MetadataStorage(md_instance));
try {
auto unavailable_instances_list(get_unavailable_instances(
*md_instance, metadata, _cluster->impl()->get_id()));
EXPECT_TRUE(unavailable_instances_list.empty());
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at get_unavailable_instances_001");
ADD_FAILURE();
}
}
TEST_F(Dba_common_cluster_functions, validate_instance_rejoinable_01) {
// There are missing instances and the instance we are checking belongs to
// the metadata list but does not belong to the GR list.
auto md_instance = create_session(_mysql_sandbox_ports[0]);
auto instance = create_session(_mysql_sandbox_ports[2]);
auto cluster_id = _cluster->impl()->get_id();
// Insert a fake record for the third instance on the metadata
std::string query =
"insert into mysql_innodb_cluster_metadata.instances "
"(instance_id, cluster_id, mysql_server_uuid, instance_name, "
"addresses, attributes, description)"
"values (0, '" +
cluster_id + "', '" + uuid_3 +
"', 'localhost:<port>', "
"'{\"mysqlX\": \"localhost:<port>0\", "
"\"grLocal\": \"localhost:1<port>\", "
"\"mysqlClassic\": \"localhost:<port>\"}', "
"NULL, NULL)";
query = shcore::str_replace(query, "<port>",
std::to_string(_mysql_sandbox_ports[2]));
md_instance->query(query);
std::shared_ptr<mysqlsh::dba::MetadataStorage> metadata;
metadata.reset(new mysqlsh::dba::MetadataStorage(md_instance));
try {
auto is_rejoinable(validate_instance_rejoinable(
*instance, metadata, _cluster->impl()->get_id()));
EXPECT_EQ(is_rejoinable, mysqlsh::dba::Instance_rejoinability::REJOINABLE);
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at validate_instance_rejoinable_01");
ADD_FAILURE();
}
md_instance->query(
"delete from mysql_innodb_cluster_metadata.instances "
" where mysql_server_uuid = '" +
uuid_3 + "'");
}
TEST_F(Dba_common_cluster_functions, validate_instance_rejoinable_02) {
// There are missing instances and the instance we are checking belongs
// to neither the metadata nor GR lists.
auto md_instance = create_session(_mysql_sandbox_ports[0]);
auto instance = create_session(_mysql_sandbox_ports[2]);
auto cluster_id = _cluster->impl()->get_id();
// Insert a fake record for the third instance on the metadata
std::string query =
"insert into mysql_innodb_cluster_metadata.instances "
"(instance_id, cluster_id, mysql_server_uuid, instance_name, "
"addresses, attributes, description)"
"values (0, '" +
cluster_id +
"', '11111111-2222-3333-4444-555555555555', 'localhost:<port>', "
"'{\"mysqlX\": \"localhost:<port>0\", "
"\"grLocal\": \"localhost:1<port>\", "
"\"mysqlClassic\": \"localhost:<port>\"}', "
"NULL, NULL)";
query = shcore::str_replace(query, "<port>",
std::to_string(_mysql_sandbox_ports[2]));
md_instance->query(query);
std::shared_ptr<mysqlsh::dba::MetadataStorage> metadata;
metadata.reset(new mysqlsh::dba::MetadataStorage(md_instance));
try {
auto is_rejoinable(validate_instance_rejoinable(
*instance, metadata, _cluster->impl()->get_id()));
EXPECT_EQ(is_rejoinable, mysqlsh::dba::Instance_rejoinability::NOT_MEMBER);
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at validate_instance_rejoinable_02");
ADD_FAILURE();
}
md_instance->query(
"delete from mysql_innodb_cluster_metadata.instances "
" where mysql_server_uuid = '11111111-2222-3333-4444-"
"555555555555'");
}
TEST_F(Dba_common_cluster_functions, validate_instance_rejoinable_03) {
// There are no missing instances and the instance we are checking belongs
// to both the metadata and GR lists.
auto md_instance = create_session(_mysql_sandbox_ports[0]);
auto instance = create_session(_mysql_sandbox_ports[1]);
std::shared_ptr<mysqlsh::dba::MetadataStorage> metadata;
metadata.reset(new mysqlsh::dba::MetadataStorage(md_instance));
try {
auto is_rejoinable(validate_instance_rejoinable(
*instance, metadata, _cluster->impl()->get_id()));
EXPECT_EQ(is_rejoinable, mysqlsh::dba::Instance_rejoinability::ONLINE);
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at validate_instance_rejoinable_03");
ADD_FAILURE();
}
}
TEST_F(Dba_common_test, super_read_only_server_on_flag_true) {
enable_replay();
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root");
auto session = mysqlshdk::db::mysql::Session::create();
session->connect(
testutil->sandbox_connection_options(_mysql_sandbox_ports[0], "root"));
// super_read_only is ON, no active sessions
session->query("set global super_read_only = 1");
try {
auto read_only = mysqlsh::dba::validate_super_read_only(
mysqlshdk::mysql::Instance(session), true);
EXPECT_TRUE(read_only);
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at super_read_only_server_on_flag_true");
ADD_FAILURE();
}
session->close();
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
}
TEST_F(Dba_common_test, super_read_only_server_on_flag_false_open_sessions) {
enable_replay();
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root");
auto session = mysqlshdk::db::mysql::Session::create();
session->connect(
testutil->sandbox_connection_options(_mysql_sandbox_ports[0], "root"));
auto extra_session = mysqlshdk::db::mysql::Session::create();
extra_session->connect(
testutil->sandbox_connection_options(_mysql_sandbox_ports[0], "root"));
// super_read_only is ON, no active sessions
session->query("set global super_read_only = 1");
try {
mysqlsh::dba::validate_super_read_only(mysqlshdk::mysql::Instance(session),
false);
SCOPED_TRACE("Unexpected success calling validate_super_read_only");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ("Server in SUPER_READ_ONLY mode", e.what());
}
session->close();
extra_session->close();
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
}
TEST_F(Dba_common_test, super_read_only_server_on_flag_false_no_open_sessions) {
enable_replay();
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root");
auto session = mysqlshdk::db::mysql::Session::create();
session->connect(
testutil->sandbox_connection_options(_mysql_sandbox_ports[0], "root"));
// super_read_only is ON, no active sessions
session->query("set global super_read_only = 1");
try {
mysqlsh::dba::validate_super_read_only(mysqlshdk::mysql::Instance(session),
false);
SCOPED_TRACE("Unexpected success calling validate_super_read_only");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ("Server in SUPER_READ_ONLY mode", e.what());
}
session->close();
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
}
TEST_F(Dba_common_test, super_read_only_server_off_flag_true) {
enable_replay();
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root");
auto session = mysqlshdk::db::mysql::Session::create();
session->connect(
testutil->sandbox_connection_options(_mysql_sandbox_ports[0], "root"));
// super_read_only is OFF, no active sessions
session->query("set global super_read_only = 0");
try {
auto read_only = mysqlsh::dba::validate_super_read_only(
mysqlshdk::mysql::Instance(session), true);
EXPECT_FALSE(read_only);
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at super_read_only_server_on_flag_true");
ADD_FAILURE();
}
session->close();
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
}
TEST_F(Dba_common_test, super_read_only_server_off_flag_false) {
enable_replay();
testutil->deploy_sandbox(_mysql_sandbox_ports[0], "root");
auto session = mysqlshdk::db::mysql::Session::create();
session->connect(
testutil->sandbox_connection_options(_mysql_sandbox_ports[0], "root"));
// super_read_only is OFF, no active sessions
session->query("set global super_read_only = 0");
try {
auto read_only = mysqlsh::dba::validate_super_read_only(
mysqlshdk::mysql::Instance(session), false);
EXPECT_FALSE(read_only);
} catch (const shcore::Exception &e) {
SCOPED_TRACE(e.what());
SCOPED_TRACE("Unexpected failure at super_read_only_server_on_flag_true");
ADD_FAILURE();
}
session->close();
testutil->destroy_sandbox(_mysql_sandbox_ports[0]);
}
TEST(mod_dba_common, validate_ipwhitelist_option) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
// NOTE: hostnames_supported = true if version >= 8.0.4, otherwise false.
auto ver_8014 = mysqlshdk::utils::Version(8, 0, 14);
auto ver_804 = mysqlshdk::utils::Version(8, 0, 4);
auto ver_800 = mysqlshdk::utils::Version(8, 0, 0);
int canonical_port = 3306;
// Error if the ipWhitelist is empty.
options.ip_allowlist_option_name = "ipWhitelist";
options.ip_allowlist = "";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ("Invalid value for ipWhitelist: string value cannot be empty.",
e.what());
}
// Error if the ipWhitelist string is empty (only whitespace).
options.ip_allowlist = " ";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ("Invalid value for ipWhitelist: string value cannot be empty.",
e.what());
}
// Error if CIDR is used but has an invalid value (not in range [1,32])
options.ip_allowlist = "192.168.1.1/0";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist '192.168.1.1/0': subnet value in CIDR "
"notation is not valid.",
e.what());
}
// Error if CIDR is used but has an invalid value (not in range [1,32])
options.ip_allowlist = "192.168.1.1/33";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist '192.168.1.1/33': subnet value in CIDR "
"notation is not supported (version >= 8.0.14 required for IPv6 "
"support).",
e.what());
}
// Error if CIDR is used but has an invalid value (not in range [1,32])
options.ip_allowlist = "1/33";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist '1/33': subnet value in CIDR "
"notation is not supported (version >= 8.0.14 required for IPv6 "
"support).",
e.what());
}
// Error if CIDR is used but has an invalid value (not in range [1,32])
// And a list of values is used
options.ip_allowlist = "192.168.1.1/0,192.168.1.1/33";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist '192.168.1.1/0': subnet value in CIDR "
"notation is not valid.",
e.what());
}
// Error if ipWhitelist is an IPv6 address and not supported by server
options.ip_allowlist = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist "
"'2001:0db8:85a3:0000:0000:8a2e:0370:7334': IPv6 not supported "
"(version >= 8.0.14 required for IPv6 support).",
e.what());
}
// Error if ipWhitelist is not a valid IPv4 address (not supported, < 8.0.4)
options.ip_allowlist = "256.255.255.255";
try {
options.check_option_values(Version(8, 0, 3), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist '256.255.255.255': hostnames are not "
"supported (version >= 8.0.4 required for hostname support).",
e.what());
}
// Error if ipWhitelist is not a valid IPv4 address
options.ip_allowlist = "256.255.255.255/16";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist '256.255.255.255/16': CIDR notation can "
"only be used with IPv4 or IPv6 addresses.",
e.what());
}
// Error if hostname is used and server version < 8.0.4
options.ip_allowlist = "localhost";
try {
options.check_option_values(Version(8, 0, 3), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist 'localhost': hostnames are not "
"supported (version >= 8.0.4 required for hostname support).",
e.what());
}
// Error if hostname with cidr
options.ip_allowlist = "localhost/8";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist 'localhost/8': CIDR notation can only "
"be used with IPv4 or IPv6 addresses.",
e.what());
}
// Error if hostname with cidr
options.ip_allowlist = "bogus/8";
try {
options.check_option_values(Version(8, 0, 4), canonical_port);
SCOPED_TRACE("Unexpected success calling validate_ip_whitelist_option");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
EXPECT_STREQ(
"Invalid value for ipWhitelist 'bogus/8': CIDR notation can only be "
"used with IPv4 or IPv6 addresses.",
e.what());
}
// No error if ipWhitelist is an IPv6 address and server does support it
options.ip_allowlist = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
{
options.check_option_values(Version(8, 0, 14), canonical_port);
SCOPED_TRACE(
"No error if ipWhitelist is an IPv6 address and server does support "
"it");
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 14), canonical_port));
}
options.ip_allowlist = "256.255.255.255";
{
// Error if ipWhitelist is not a valid IPv4 address and version < 8.0.4
// since it is not a valid IPv4 it assumes it must be an hostname and
// hostnames are not supported below 8.0.4
SCOPED_TRACE(
"Error if ipWhitelist is not a valid IPv4 address and version < 8.0.4");
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 0), canonical_port),
shcore::Exception,
"Invalid value for ipWhitelist '256.255.255.255': hostnames are not "
"supported (version >= 8.0.4 required for hostname support).");
}
{
// Error if hostname is used and server version < 8.0.4
options.ip_allowlist = "localhost";
SCOPED_TRACE("Error if hostname is used and server version < 8.0.4");
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 0), canonical_port),
shcore::Exception,
"Invalid value for ipWhitelist 'localhost': hostnames are not "
"supported (version >= 8.0.4 required for hostname support).");
}
{
// No error if hostname is used and server version >= 8.0.4 because
// we don't do hostname resolution
SCOPED_TRACE("No error if hostname is used and server version >= 8.0.4");
options.ip_allowlist = "fake_hostnanme";
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 4), canonical_port));
}
// No error if the ipWhitelist is a valid IPv4 address
options.ip_allowlist = "192.168.1.1";
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 4), canonical_port));
// No error if the ipWhitelist is a valid IPv4 address with a valid CIDR value
options.ip_allowlist = "192.168.1.1/15";
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 4), canonical_port));
// No error if the ipWhitelist consist of several valid IPv4 addresses with a
// valid CIDR value
// NOTE: if the server version is > 8.0.4, hostnames are allowed too so we
// must test it
options.ip_allowlist = "192.168.1.1/15,192.169.1.1/1, localhost";
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 4), canonical_port));
options.ip_allowlist = "192.168.1.1/15,192.169.1.1/1";
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 3), canonical_port));
options.ip_allowlist =
"2001:0db8:85a3:0000:0000:8a2e:0370:7334/16,192.168.1.1/15,192.169.1.1/1";
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 14), canonical_port));
}
TEST(mod_dba_common, validate_exit_state_action_supported) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
options.exit_state_action = "1";
int canonical_port = 3306;
// Error only if the target server version is >= 5.7.24 if 5.0, or >= 8.0.12
// if 8.0.
EXPECT_THROW_LIKE(
options.check_option_values(Version(5, 7, 23), canonical_port),
shcore::Exception,
"Option 'exitStateAction' not supported on target server "
"version:");
EXPECT_NO_THROW(
options.check_option_values(Version(5, 7, 24), canonical_port));
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 11), canonical_port),
shcore::Exception,
"Option 'exitStateAction' not supported on target server "
"version:");
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 12), canonical_port));
}
TEST(mod_dba_common, validate_member_weight_supported) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
options.member_weight = 1;
int canonical_port = 3306;
// Error only if the target server version is < 5.7.20 if 5.0, or < 8.0.11
// if 8.0.
EXPECT_THROW_LIKE(
options.check_option_values(Version(5, 7, 19), canonical_port),
shcore::Exception,
"Option 'memberWeight' not supported on target server "
"version:");
EXPECT_NO_THROW(
options.check_option_values(Version(5, 7, 20), canonical_port));
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 10), canonical_port),
shcore::Exception,
"Option 'memberWeight' not supported on target server "
"version:");
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 11), canonical_port));
}
TEST(mod_dba_common, validate_consistency_supported) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
Version version(8, 0, 14);
int canonical_port = 3306;
std::optional<std::string> empty_fail_cons{" "};
std::optional<std::string> valid_fail_cons{"1"};
options.consistency = std::nullopt;
// if a null value was provided, it is as if the option was not provided,
// so no error should be thrown
options.check_option_values(version, canonical_port);
options.consistency = empty_fail_cons;
// if an empty value was provided, an error should be thrown independently
// of the server version
EXPECT_THROW_LIKE(
options.check_option_values(version, canonical_port), shcore::Exception,
"Invalid value for consistency, string value cannot be empty.");
// if a valid value (non empty) was provided, an error should only be thrown
// in case the option is not supported by the server version.
options.consistency = valid_fail_cons;
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 13), canonical_port),
std::runtime_error,
"Option 'consistency' not supported on target server "
"version:");
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 14), canonical_port));
}
TEST(mod_dba_common, validate_auto_rejoin_tries_supported) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
options.auto_rejoin_tries = 1;
int canonical_port = 3306;
// Error only if the target server version is < 8.0.16
EXPECT_THROW_LIKE(
options.check_option_values(Version(5, 7, 19), canonical_port),
shcore::Exception,
"Option 'autoRejoinTries' not supported on target server "
"version:");
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 15), canonical_port),
shcore::Exception,
"Option 'autoRejoinTries' not supported on target server "
"version:");
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 16), canonical_port));
}
TEST(mod_dba_common, validate_expel_timeout_supported) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
Version version(8, 0, 13);
int canonical_port = 3306;
std::optional<std::int64_t> valid_timeout{3600};
std::optional<std::int64_t> maybe_valid_timeout{3601};
// if a null value was provided, it is as if the option was not provided,
// so no error should be thrown
options.expel_timeout = std::nullopt;
options.check_option_values(version, canonical_port);
// if a value non in the allowed range value was provided, an error should be
// thrown independently of the server version
// this value is "valid" only for versions > 8.0.13
options.expel_timeout = maybe_valid_timeout;
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 13), canonical_port),
std::runtime_error,
"Invalid value for 'expelTimeout': integer value must be "
"in the range [0, 3600]");
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 14), canonical_port));
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 15), canonical_port));
// if a valid value was provided, an error should only be thrown
// in case the option is not supported by the server version.
options.expel_timeout = valid_timeout;
EXPECT_THROW_LIKE(
options.check_option_values(Version(8, 0, 12), canonical_port),
std::runtime_error,
"Option 'expelTimeout' not supported on target server "
"version:");
options.expel_timeout = valid_timeout;
EXPECT_NO_THROW(
options.check_option_values(Version(8, 0, 13), canonical_port));
}
TEST(mod_dba_common, is_option_supported) {
// if a non supported version is used, then we must throw an exception,
// else just save the result for further testing
EXPECT_THROW_LIKE(
mysqlsh::dba::is_option_supported(
Version(9, 0, 0), mysqlsh::dba::kExitStateAction,
mysqlsh::dba::k_global_cluster_supported_options),
std::runtime_error,
"Unexpected version found for option support check: '9.0.0'.");
// testing the result of exit-state action case since it has requirements for
// both 5.7 and the 8.0 MySQL versions.
EXPECT_FALSE(mysqlsh::dba::is_option_supported(
Version(8, 0, 11), mysqlsh::dba::kExitStateAction,
mysqlsh::dba::k_global_cluster_supported_options));
EXPECT_TRUE(mysqlsh::dba::is_option_supported(
Version(8, 0, 12), mysqlsh::dba::kExitStateAction,
mysqlsh::dba::k_global_cluster_supported_options));
EXPECT_FALSE(mysqlsh::dba::is_option_supported(
Version(5, 7, 23), mysqlsh::dba::kExitStateAction,
mysqlsh::dba::k_global_cluster_supported_options));
EXPECT_TRUE(mysqlsh::dba::is_option_supported(
Version(5, 7, 24), mysqlsh::dba::kExitStateAction,
mysqlsh::dba::k_global_cluster_supported_options));
// testing the result of autoRejoinRetries which is only supported on 8.0.16
// onwards (BUG#29246657)
EXPECT_FALSE(mysqlsh::dba::is_option_supported(
Version(8, 0, 11), mysqlsh::dba::kAutoRejoinTries,
mysqlsh::dba::k_global_cluster_supported_options));
EXPECT_TRUE(mysqlsh::dba::is_option_supported(
Version(8, 0, 16), mysqlsh::dba::kAutoRejoinTries,
mysqlsh::dba::k_global_cluster_supported_options));
EXPECT_FALSE(mysqlsh::dba::is_option_supported(
Version(5, 7, 23), mysqlsh::dba::kAutoRejoinTries,
mysqlsh::dba::k_global_cluster_supported_options));
}
TEST(mod_dba_common, validate_local_address_option) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
Version version(8, 0, 14);
int canonical_port = 3306;
// Error if the localAddress is empty.
options.local_address = "";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
// Error if the localAddress string is empty (only whitespace).
options.local_address = " ";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
// Error if the localAddress has ':' and no host nor port part is specified.
options.local_address = " : ";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
// No error if the localAddress is a non-empty string.
options.local_address = "myhost:1234";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
options.local_address = "myhost:";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
options.local_address = ":1234";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
options.local_address = "myhost";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
options.local_address = "1234";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
}
TEST(mod_dba_common, validate_local_address_option_mysql_comm_stack) {
using mysqlsh::dba::Group_replication_options;
Group_replication_options options;
Version version(8, 0, 30);
int canonical_port = 3306;
options.communication_stack = mysqlsh::dba::kCommunicationStackMySQL;
// Error if the localAddress is empty.
options.local_address = "";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
// Error if the localAddress string is empty (only whitespace).
options.local_address = " ";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
// Error if the localAddress has ':' and no host nor port part is specified.
options.local_address = " : ";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
// No Error if the port is not specified
options.local_address = "myhost:";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
options.local_address = "myhost";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
// Error if the port is not the canonical port
options.local_address = "myhost:3300";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
options.local_address = ":3300";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
options.local_address = "3300";
EXPECT_THROW(options.check_option_values(version, canonical_port),
shcore::Exception);
// No error if the localAddress is using the canonical port
options.local_address = "myhost:3306";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
options.local_address = ":3306";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
options.local_address = "3306";
EXPECT_NO_THROW(options.check_option_values(version, canonical_port));
}
TEST(mod_dba_common, validate_label) {
std::string t{};
EXPECT_NO_THROW(
// Valid label, begins with valid synbols (alpha)
t = "Valid1"; mysqlsh::dba::validate_label(t.c_str()));
EXPECT_NO_THROW(
// Valid label, begins with valid synbols (_)
t = "_Valid_"; mysqlsh::dba::validate_label(t.c_str()));
EXPECT_NO_THROW(
// Valid label, contains valid synbols
t = "Valid_3"; mysqlsh::dba::validate_label(t.c_str()));
EXPECT_NO_THROW(
// Valid label, contains valid synbols (:.-)
t = "Valid:.-4"; mysqlsh::dba::validate_label(t.c_str()));
EXPECT_NO_THROW(
// Valid label, begins with valid synbols (numeric)
t = "2_Valid"; mysqlsh::dba::validate_label(t.c_str()));
EXPECT_ANY_THROW(t = "";
// Invalid empty label
mysqlsh::dba::validate_label(t.c_str()););
EXPECT_ANY_THROW(
// Invalid label, contains invalid synbol
t = "not_allowed?"; mysqlsh::dba::validate_label(t.c_str()));
EXPECT_ANY_THROW(
// Invalid label, contains invalid synbol
t = "(not*valid)"; mysqlsh::dba::validate_label("(not_valid)"));
EXPECT_ANY_THROW(
// Invalid too long label (over 256 characteres)
t = "over256chars_"
"12345678901234567890123456789901234567890123456789012345678901234567"
"89012345678901234567890123456789012345678901234567890123456789012345"
"67890123456789012345678901234567890123456789012345678901234567890123"
"4567890123456789012345678901234567890123";
mysqlsh::dba::validate_label(t.c_str()););
EXPECT_ANY_THROW(
// Invalid label, begins with invalid synbol
t = "#not_allowed"; mysqlsh::dba::validate_label(t.c_str()););
EXPECT_ANY_THROW(
// Invalid label, contains invalid synbol
t = "_not-allowed?"; mysqlsh::dba::validate_label(t.c_str()););
EXPECT_ANY_THROW(
// Invalid label, contains invalid synbol
t = "(*)%?"; mysqlsh::dba::validate_label(t.c_str()););
}
TEST(mod_dba_common, is_valid_identifier) {
std::string t{};
EXPECT_NO_THROW(
// Valid identifier, begins with valid characters (alpha)
t = "Valid1"; mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
EXPECT_NO_THROW(
// Valid identifier, begins with valid characters (_)
t = "_Valid_"; mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
EXPECT_NO_THROW(
// Valid identifier, contains valid characters (_, -, .)
t = "Valid_3.still-valid"; mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
EXPECT_ANY_THROW(t = "";
// Invalid empty identifier
mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION););
EXPECT_ANY_THROW(
// Invalid too long identifier (over 63 characters)
t = "o-ver63ch.ars_12345678901234567890123456789013456789012345678901";
mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION););
EXPECT_ANY_THROW(
// Invalid identifier, begins with invalid character
t = "#not_allowed"; mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION););
EXPECT_ANY_THROW(
// Invalid identifier, contains invalid character
t = "not_allowed?"; mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION););
EXPECT_ANY_THROW(
// Invalid identifier, begins with invalid characters (numeric)
t = "2_not_Valid"; mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION));
EXPECT_ANY_THROW(
// Invalid identifier, contains invalid character
t = "(*)%?"; mysqlsh::dba::validate_cluster_name(
t, mysqlsh::dba::Cluster_type::GROUP_REPLICATION););
}
TEST_F(Dba_common_test, resolve_gr_local_address) {
std::optional<std::string> local_address;
std::string raw_report_host = "127.0.0.1";
std::optional<std::string> communication_stack;
int port;
// Tests for empty localAddress
// Valid port, local_address null
{
local_address = std::nullopt;
port = 3306;
EXPECT_NO_THROW(mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true));
}
// Valid port, local_address empty
{
local_address = std::string("");
port = 3306;
EXPECT_NO_THROW(mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true));
}
// Invalid port, local_address null
{
local_address = std::nullopt;
port = 13040;
EXPECT_THROW_LIKE(
mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true),
shcore::Exception,
"Automatically generated port for localAddress falls out of valid "
"range. The port must be an integer between 1 and 65535. Please use "
"the localAddress option to manually set a valid value.");
}
// Invalid port, local_address empty
{
local_address = std::string("");
port = 13040;
EXPECT_THROW_LIKE(
mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true),
shcore::Exception,
"Automatically generated port for localAddress falls out of valid "
"range. The port must be an integer between 1 and 65535. Please use "
"the localAddress option to manually set a valid value.");
}
// Busy port
{
testutil->deploy_sandbox(_mysql_sandbox_ports[0] * 10 + 1, "root");
local_address = std::string("");
port = _mysql_sandbox_ports[0];
try {
mysqlsh::dba::resolve_gr_local_address(local_address, communication_stack,
raw_report_host, port, true);
testutil->destroy_sandbox(_mysql_sandbox_ports[0] * 10 + 1);
SCOPED_TRACE("Unexpected success calling resolve_gr_local_address");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
std::string expected =
"The port '" + std::to_string(port * 10 + 1) +
"' for localAddress option is already in use. Specify an "
"available port to be used with localAddress option or free port "
"'" +
std::to_string(port * 10 + 1) + "'.";
EXPECT_STREQ(expected.c_str(), e.what());
}
}
// Tests for non-empty localAddress
// Valid port, non-empty local_address
{
local_address = std::string("127.0.0.1");
port = 3306;
EXPECT_NO_THROW(mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true));
}
// Invalid port, complete local_address
{
local_address = std::string("127.0.0.1:130400");
EXPECT_THROW_LIKE(
mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true),
shcore::Exception,
"Invalid port '130400' for localAddress option. The port must be an "
"integer between 1 and 65535.");
}
// Invalid port (string), complete local_address
{
local_address = std::string("127.0.0.1:a");
EXPECT_THROW_LIKE(
mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true),
shcore::Exception,
"Invalid port 'a' for localAddress option. The port must be an "
"integer between 1 and 65535.");
}
// Invalid port, local_address without port part
{
local_address = std::string("127.0.0.2");
port = 13040;
EXPECT_THROW_LIKE(
mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true),
shcore::Exception,
"Automatically generated port for localAddress falls out of valid "
"range. The port must be an integer between 1 and 65535. Please use "
"the localAddress option to manually set a valid value.");
}
// Invalid port, local_address without port part, using ":"
{
local_address = std::string("127.0.0.2:");
port = 13040;
EXPECT_THROW_LIKE(
mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true),
shcore::Exception,
"Automatically generated port for localAddress falls out of valid "
"range. The port must be an integer between 1 and 65535. Please use "
"the localAddress option to manually set a valid value.");
}
// Valid port, local_address without separator ':', assumed to be the port
{
int unused_port = _mysql_sandbox_ports[1];
local_address = std::to_string(unused_port);
EXPECT_NO_THROW(mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true));
}
// Invalid port, local_address without separator ':', assumed to be the port
{
local_address = std::string("130401");
EXPECT_THROW_LIKE(
mysqlsh::dba::resolve_gr_local_address(
local_address, communication_stack, raw_report_host, port, true),
shcore::Exception,
"Invalid port '130401' for localAddress option. The port must be an "
"integer between 1 and 65535.");
}
// Busy port
{
int used_port = _mysql_sandbox_ports[0] * 10 + 1;
local_address = std::string("127.0.0.1:") + std::to_string(used_port);
try {
mysqlsh::dba::resolve_gr_local_address(local_address, communication_stack,
raw_report_host, port, true);
testutil->destroy_sandbox(_mysql_sandbox_ports[0] * 10 + 1);
SCOPED_TRACE("Unexpected success calling resolve_gr_local_address");
ADD_FAILURE();
} catch (const shcore::Exception &e) {
std::string expected =
"The port '" + std::to_string(used_port) +
"' for localAddress option is already in use. Specify an "
"available port to be used with localAddress option or free port "
"'" +
std::to_string(used_port) + "'.";
EXPECT_STREQ(expected.c_str(), e.what());
}
}
testutil->destroy_sandbox(_mysql_sandbox_ports[0] * 10 + 1);
}
TEST_F(Dba_common_test, set_wait_recovery) {
mysqlsh::dba::cluster::Add_instance_options options;
options.set_wait_recovery(mysqlsh::dba::kWaitRecovery, 0);
EXPECT_EQ(options.get_wait_recovery(),
mysqlsh::dba::Recovery_progress_style::NOWAIT);
options.set_wait_recovery(mysqlsh::dba::kWaitRecovery, 1);
EXPECT_EQ(options.get_wait_recovery(),
mysqlsh::dba::Recovery_progress_style::NOINFO);
options.set_wait_recovery(mysqlsh::dba::kWaitRecovery, 2);
EXPECT_EQ(options.get_wait_recovery(),
mysqlsh::dba::Recovery_progress_style::TEXTUAL);
options.set_wait_recovery(mysqlsh::dba::kWaitRecovery, 3);
EXPECT_EQ(options.get_wait_recovery(),
mysqlsh::dba::Recovery_progress_style::PROGRESSBAR);
options.set_wait_recovery(mysqlsh::dba::kRecoveryProgress, 0);
EXPECT_EQ(options.get_wait_recovery(),
mysqlsh::dba::Recovery_progress_style::NOINFO);
options.set_wait_recovery(mysqlsh::dba::kRecoveryProgress, 1);
EXPECT_EQ(options.get_wait_recovery(),
mysqlsh::dba::Recovery_progress_style::TEXTUAL);
options.set_wait_recovery(mysqlsh::dba::kRecoveryProgress, 2);
EXPECT_EQ(options.get_wait_recovery(),
mysqlsh::dba::Recovery_progress_style::PROGRESSBAR);
}
} // namespace testing