unittest/shell_js_dba_t.cc (266 lines of code) (raw):
/*
* Copyright (c) 2015, 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
*/
#ifdef _WIN32
#include <windows.h>
#endif
#include <algorithm>
#include "modules/adminapi/common/sql.h"
#include "modules/mod_mysql_session.h"
#include "mysqlshdk/libs/db/connection_options.h"
#include "mysqlshdk/libs/db/mysql/session.h"
#include "mysqlshdk/libs/db/replay/setup.h"
#include "mysqlshdk/libs/mysql/instance.h"
#include "mysqlshdk/libs/utils/utils_stacktrace.h"
#include "mysqlshdk/libs/utils/utils_string.h"
#include "shell_script_tester.h"
#include "shellcore/base_session.h"
#include "utils/utils_file.h"
#include "utils/utils_general.h"
#include "utils/utils_path.h"
namespace shcore {
class Shell_js_dba_tests : public Shell_js_script_tester {
protected:
bool _have_ssl;
std::string _sandbox_share;
static bool have_sandboxes;
// You can define per-test set-up and tear-down logic as usual.
virtual void SetUp() {
Shell_js_script_tester::SetUp();
// All of the test cases share the same config folder
// and setup script
set_config_folder("js_devapi");
set_setup_script("setup.js");
}
virtual void set_defaults() {
Shell_js_script_tester::set_defaults();
std::string user, host, password;
auto connection_options = shcore::get_connection_options(_uri);
if (connection_options.has_user()) user = connection_options.get_user();
if (connection_options.has_host()) host = connection_options.get_host();
if (connection_options.has_password())
password = connection_options.get_password();
std::string mysql_uri = "mysql://";
std::string have_ssl;
_have_ssl = true;
assert(!m_port.empty());
assert(!m_mysql_port.empty());
std::string code = "var hostname = '" + hostname() + "';";
exec_and_out_equals(code);
code = "var real_hostname = '" + real_hostname() + "';";
exec_and_out_equals(code);
if (real_host_is_loopback())
code = "var real_host_is_loopback = true;";
else
code = "var real_host_is_loopback = false;";
exec_and_out_equals(code);
code = "var hostname_ip = '" + hostname_ip() + "';";
exec_and_out_equals(code);
code = "var __user = '" + user + "';";
exec_and_out_equals(code);
code = "var __host = '" + host + "';";
exec_and_out_equals(code);
code = "var __port = " + m_port + ";";
exec_and_out_equals(code);
code = "var __schema = 'mysql';";
exec_and_out_equals(code);
code = "var __uri = '" + user + "@" + host + ":" + m_port + "';";
exec_and_out_equals(code);
code = "var __xhost_port = '" + host + ":" + m_port + "';";
exec_and_out_equals(code);
code = "var __host_port = '" + host + ":" + m_mysql_port + "';";
exec_and_out_equals(code);
code = "var __mysql_port = " + m_mysql_port + ";";
exec_and_out_equals(code);
code = shcore::str_format("var __secure_password = '%.*s';",
static_cast<int>(k_secure_password.length()),
k_secure_password.data());
exec_and_out_equals(code);
for (int i = 0; i < tests::sandbox::k_num_ports; i++) {
code = shcore::str_format("var __mysql_sandbox_port%i = %i;", i + 1,
_mysql_sandbox_ports[i]);
exec_and_out_equals(code);
// With 8.0.27, the GR Protocol Communication Stack became MySQL by
// default and localAddress is set to the server port by default
if (_target_server_version < mysqlshdk::utils::Version("8.0.27")) {
code = shcore::str_format("var __mysql_sandbox_gr_port%i = %i;", i + 1,
_mysql_sandbox_ports[i] * 10 + 1);
} else {
code = shcore::str_format("var __mysql_sandbox_gr_port%i = %i;", i + 1,
_mysql_sandbox_ports[i]);
}
exec_and_out_equals(code);
code = shcore::str_format(
"var __sandbox_uri%i = 'mysql://root:root@localhost:%i';", i + 1,
_mysql_sandbox_ports[i]);
exec_and_out_equals(code);
code = shcore::str_format(
"var __sandbox_uri_secure_password%i = "
"'mysql://root:%.*s@localhost:%i';",
i + 1, static_cast<int>(k_secure_password.length()),
k_secure_password.data(), _mysql_sandbox_ports[i]);
exec_and_out_equals(code);
code = shcore::str_format(
"var __hostname_uri%i = 'mysql://root:root@%s:%i';", i + 1,
hostname().c_str(), _mysql_sandbox_ports[i]);
exec_and_out_equals(code);
code = shcore::str_format("var uri%i = 'localhost:%i';", i + 1,
_mysql_sandbox_ports[i]);
exec_and_out_equals(code);
}
code = "var localhost = 'localhost'";
exec_and_out_equals(code);
code =
"var add_instance_options = {HoSt:localhost, port: 0000, "
"PassWord:'root', scheme:'mysql'};";
exec_and_out_equals(code);
code = "var add_instance_extra_opts = {};";
exec_and_out_equals(code);
_sandbox_share = shcore::path::join_path(_sandbox_dir, "sandbox.share");
#ifdef _WIN32
code = "var __path_splitter = '\\\\';";
exec_and_out_equals(code);
auto tokens = shcore::split_string(_sandbox_dir, "\\");
if (!tokens.at(tokens.size() - 1).empty()) tokens.push_back("");
// The sandbox dir for C++
_sandbox_dir = shcore::str_join(tokens, "\\");
// The sandbox dir for JS
code = "var __sandbox_dir = '" + shcore::str_join(tokens, "\\\\") + "';";
exec_and_out_equals(code);
// output sandbox dir
_output_tokens["__output_sandbox_dir"] = shcore::str_join(tokens, "\\");
tokens = shcore::split_string(_sandbox_share, "\\");
code = "var __sandbox_share = '" + shcore::str_join(tokens, "\\\\") + "'";
exec_and_out_equals(code);
#else
code = "var __path_splitter = '/';";
exec_and_out_equals(code);
if (_sandbox_dir.back() != '/') {
code = "var __sandbox_dir = '" + _sandbox_dir + "/';";
exec_and_out_equals(code);
code = "var __output_sandbox_dir = '" + _sandbox_dir + "/';";
exec_and_out_equals(code);
} else {
code = "var __sandbox_dir = '" + _sandbox_dir + "';";
exec_and_out_equals(code);
code = "var __output_sandbox_dir = '" + _sandbox_dir + "';";
exec_and_out_equals(code);
}
code = "var __sandbox_share = '" + _sandbox_share + "';";
exec_and_out_equals(code);
#endif
code = "var __uripwd = '" + user + ":" + password + "@" + host + ":" +
m_port + "';";
exec_and_out_equals(code);
code = "var __mysqluripwd = '" + user + ":" + password + "@" + host + ":" +
m_mysql_port + "';";
exec_and_out_equals(code);
if (_replaying)
code = "var __replaying = true;";
else
code = "var __replaying = false;";
exec_and_out_equals(code);
if (_recording)
code = "var __recording = true;";
else
code = "var __recording = false;";
exec_and_out_equals(code);
}
};
bool Shell_js_dba_tests::have_sandboxes = true;
TEST_F(Shell_js_dba_tests, no_active_session_error) {
_options->wizards = false;
reset_shell();
execute("var c = dba.getCluster()");
MY_EXPECT_STDERR_CONTAINS(
"An open session is required to perform this operation.");
wipe_all();
execute("dba.verbose = true;");
EXPECT_EQ("", output_handler.std_err);
_options->wizards = true;
reset_shell();
execute("var c = dba.getCluster()");
MY_EXPECT_STDERR_CONTAINS(
"An open session is required to perform this operation.");
wipe_all();
execute("dba.verbose = true;");
EXPECT_EQ("", output_handler.std_err);
}
// Sandbox specific tests should not do session replay
TEST_F(Shell_js_dba_tests, no_interactive_sandboxes) {
_options->wizards = false;
reset_shell();
output_handler.set_log_level(shcore::Logger::LOG_WARNING);
execute("dba.verbose = true;");
// Create directory with space and quotes in name to test.
#ifdef _WIN32
std::string path_splitter = "\\";
#else
std::string path_splitter = "/";
#endif
// Note: not tested with " in the folder name because windows does not
// support the creation of directories with: <>:"/\|?*
std::string dir = _sandbox_dir + path_splitter + "foo \' bar";
shcore::ensure_dir_exists(dir);
// Create directory with long name (> 89 char).
std::string dir_long = _sandbox_dir + path_splitter +
"01234567891123456789212345678931234567894123456789"
"5123456789612345678971234567898123456789";
shcore::ensure_dir_exists(dir_long);
// Create directory with non-ascii characteres.
std::string dir_non_ascii =
_sandbox_dir + path_splitter + "no_café_para_los_niños";
shcore::ensure_dir_exists(dir_non_ascii);
validate_interactive("dba_sandboxes.js");
// Remove previously created directories.
shcore::remove_directory(dir);
shcore::remove_directory(dir_long);
shcore::remove_directory(dir_non_ascii);
// BUG#26393614
std::vector<std::string> log{
"Sandbox instances are only suitable for deploying and "
"running on your local machine for testing purposes and are not "
"accessible from external networks."};
MY_EXPECT_LOG_CONTAINS(log);
}
TEST_F(Shell_js_dba_tests, interactive_deploy_instance) {
_options->interactive = true;
reset_shell();
output_handler.set_log_level(shcore::Logger::LOG_WARNING);
// BUG 26830224
// Please enter a MySQL root password for the new instance:
output_handler.passwords.push_back({"*", "root", {}});
validate_interactive("dba_deploy_sandbox.js");
// BUG#26393614
std::vector<std::string> log{
"Sandbox instances are only suitable for deploying and "
"running on your local machine for testing purposes and are not "
"accessible from external networks."};
MY_EXPECT_LOG_CONTAINS(log);
}
TEST_F(Shell_js_dba_tests, no_interactive) {
std::string bad_config = "[mysqld]\ngtid_mode = OFF\n";
create_file("mybad.cnf", bad_config);
_options->wizards = false;
reset_replayable_shell();
// Validates error conditions on create, get and drop cluster
// Lets the cluster created
validate_interactive("dba_no_interactive.js");
}
TEST_F(Shell_js_dba_tests, cluster_multimaster_no_interactive) {
_options->wizards = false;
reset_replayable_shell();
// Tests cluster functionality, adding, removing instances
// error conditions
// Lets the cluster empty
validate_interactive("dba_cluster_multimaster_no_interactive.js");
}
TEST_F(Shell_js_dba_tests, interactive) {
// IMPORTANT NOTE: This test fixture requires non sandbox server as the base
// server.
std::string bad_config = "[mysqld]\ngtid_mode = OFF\n";
create_file("mybad.cnf", bad_config);
_options->interactive = true;
reset_replayable_shell();
// Validates error conditions on create, get and drop cluster
// Lets the cluster created
validate_interactive("dba_interactive.js");
}
TEST_F(Shell_js_dba_tests, cluster_multimaster_interactive) {
_options->interactive = true;
reset_replayable_shell();
//@<OUT> Dba: createCluster multiPrimary with interaction, cancel
output_handler.prompts.push_back({"*", "no", {}});
//@<OUT> Dba: createCluster multiPrimary with interaction, ok
output_handler.prompts.push_back({"*", "yes", {}});
//@ Dissolve cluster
output_handler.prompts.push_back({"*", "yes", {}});
//@<OUT> Dba: createCluster multiMaster with interaction, regression for
// BUG#25926603
output_handler.prompts.push_back({"*", "yes", {}});
//@ Dissolve cluster with success
output_handler.prompts.push_back({"*", "yes", {}});
//@<OUT> Dba: createCluster multiMaster with interaction 2, ok
output_handler.prompts.push_back({"*", "yes", {}});
//@: Cluster: rejoinInstance errors
output_handler.passwords.push_back({"*", "root", {}});
output_handler.passwords.push_back({"*", "root", {}});
// @<OUT> Cluster: status for rejoin: success
// Dissolve cluster.
output_handler.prompts.push_back({"*", "yes", {}});
// Tests cluster functionality, adding, removing instances
// error conditions.
validate_interactive("dba_cluster_multimaster_interactive.js");
}
TEST_F(Shell_js_dba_tests, dba_cluster_mts) {
_options->wizards = false;
reset_replayable_shell();
std::string bad_config = "[mysqld]\ngtid_mode = OFF\n";
create_file("mybad.cnf", bad_config);
validate_interactive("dba_cluster_mts.js");
}
TEST_F(Shell_js_dba_tests, super_read_only_handling) {
reset_replayable_shell();
//@<OUT> Configures the instance, answers 'yes' on the read only prompt
output_handler.prompts.push_back({"*", "y", {}});
//@ Reboot the cluster
// Confirms addition of second instance
output_handler.prompts.push_back({"*", "y", {}});
// Confirms addition of third instance
output_handler.prompts.push_back({"*", "y", {}});
validate_interactive("dba_super_read_only_handling.js");
}
} // namespace shcore