unittest/mysqlshdk/libs/gr/group_replication_t.cc (1,660 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 <map> #include <string> #include <utility> #include <vector> #include "modules/adminapi/common/common.h" #include "mysqlshdk/include/scripting/types.h" #include "mysqlshdk/libs/config/config.h" #include "mysqlshdk/libs/config/config_file_handler.h" #include "mysqlshdk/libs/config/config_server_handler.h" #include "mysqlshdk/libs/db/mysql/session.h" #include "mysqlshdk/libs/db/session.h" #include "mysqlshdk/libs/mysql/group_replication.h" #include "mysqlshdk/libs/mysql/repl_config.h" #include "mysqlshdk/libs/utils/utils_file.h" #include "mysqlshdk/libs/utils/utils_general.h" #include "mysqlshdk/libs/utils/utils_path.h" #include "mysqlshdk/libs/utils/utils_sqlstring.h" #include "unittest/test_utils/mocks/mysqlshdk/libs/db/mock_session.h" #include "unittest/test_utils/mocks/mysqlshdk/libs/mysql/mock_instance.h" #include "unittest/test_utils/shell_base_test.h" namespace mysqlsh { namespace dba { extern void validate_ip_whitelist_option( const mysqlshdk::utils::Version &version, const std::string &ip_whitelist, const std::string &ip_allowlist_option_name); } // namespace dba } // namespace mysqlsh namespace testing { class Group_replication_test : public tests::Shell_base_test { protected: void SetUp() { tests::Shell_base_test::SetUp(); // Create instance and Open the session for the tests. _connection_options = shcore::get_connection_options(_mysql_uri); _session->connect(_connection_options); m_instance = new mysqlshdk::mysql::Instance(_session); // Get temp dir path m_tmpdir = getenv("TMPDIR"); m_cfg_path = shcore::path::join_path(m_tmpdir, "my_gr_test.cnf"); } void TearDown() { tests::Shell_base_test::TearDown(); // Close the session. _session->close(); delete m_instance; } std::shared_ptr<mysqlshdk::db::ISession> _session = mysqlshdk::db::mysql::Session::create(); mysqlshdk::mysql::Instance *m_instance; mysqlshdk::db::Connection_options _connection_options; std::string m_tmpdir, m_cfg_path; }; TEST_F(Group_replication_test, plugin_installation) { // Check if GR plugin is installed and uninstall it. std::optional<std::string> init_plugin_state = m_instance->get_plugin_status(mysqlshdk::gr::k_gr_plugin_name); if (init_plugin_state.has_value()) { // Test uninstall the plugin when available. bool res = mysqlshdk::gr::uninstall_group_replication_plugin(*m_instance, nullptr); EXPECT_TRUE(res); std::optional<std::string> plugin_state = m_instance->get_plugin_status(mysqlshdk::gr::k_gr_plugin_name); ASSERT_TRUE(!plugin_state.has_value()); // Test trying to uninstall the plugin when not available. res = mysqlshdk::gr::uninstall_group_replication_plugin(*m_instance, nullptr); EXPECT_FALSE(res); } // Test installing the plugin (when not installed). if (init_plugin_state.has_value() && (init_plugin_state == "DISABLED")) { // An exception is expected if the plugin was disabled. EXPECT_THROW( mysqlshdk::gr::install_group_replication_plugin(*m_instance, nullptr), std::runtime_error); } else { // Requirements to install the GR plugin: // - server_id != 0 // - master_info_repository=TABLE // - relay_log_info_repository=TABLE std::optional<int64_t> server_id = m_instance->get_sysvar_int("server_id"); if (*server_id == 0) { SKIP_TEST("Test server does not meet GR requirements: server_id is 0."); } std::optional<std::string> master_info_repository = m_instance->get_sysvar_string("master_info_repository"); if ((*master_info_repository).compare("TABLE") != 0) { SKIP_TEST( "Test server does not meet GR requirements: master_info_repository " "must be 'TABLE'."); } std::optional<std::string> relay_log_info_repository = m_instance->get_sysvar_string("relay_log_info_repository"); if ((*relay_log_info_repository).compare("TABLE") != 0) { SKIP_TEST( "Test server does not meet GR requirements: " "relay_log_info_repository " "must be 'TABLE'."); } // GR plugin is installed and activated (if not previously disabled). bool res = mysqlshdk::gr::install_group_replication_plugin(*m_instance, nullptr); ASSERT_TRUE(res) << "GR plugin was not installed (expected not to be available)."; std::optional<std::string> plugin_state = m_instance->get_plugin_status(mysqlshdk::gr::k_gr_plugin_name); EXPECT_STREQ("ACTIVE", (*plugin_state).c_str()); // Test installing the plugin when already installed. res = mysqlshdk::gr::install_group_replication_plugin(*m_instance, nullptr); EXPECT_FALSE(res) << "GR plugin was installed (expected to be already available)."; plugin_state = m_instance->get_plugin_status(mysqlshdk::gr::k_gr_plugin_name); EXPECT_STREQ("ACTIVE", (*plugin_state).c_str()); } // Restore initial state (uninstall plugin if needed). if (!init_plugin_state.has_value()) { // Test uninstall the plugin when available. bool res = mysqlshdk::gr::uninstall_group_replication_plugin(*m_instance, nullptr); EXPECT_TRUE(res); std::optional<std::string> plugin_state = m_instance->get_plugin_status(mysqlshdk::gr::k_gr_plugin_name); ASSERT_TRUE(!plugin_state.has_value()); // Test trying to uninstall the plugin when not available. res = mysqlshdk::gr::uninstall_group_replication_plugin(*m_instance, nullptr); EXPECT_FALSE(res); } } TEST_F(Group_replication_test, generate_uuid) { std::string name1 = mysqlshdk::gr::generate_uuid(*m_instance); std::string name2 = mysqlshdk::gr::generate_uuid(*m_instance); // Generated group names must be different. EXPECT_STRNE(name1.c_str(), name2.c_str()); } TEST_F(Group_replication_test, create_recovery_user) { // Confirm that there is no replication user. auto res = mysqlshdk::gr::check_replication_user(*m_instance, "test_gr_user", "%"); EXPECT_FALSE(res.user_exists()); EXPECT_EQ(std::set<std::string>{"REPLICATION SLAVE"}, res.missing_privileges()); EXPECT_TRUE(res.has_missing_privileges()); EXPECT_FALSE(res.has_grant_option()); // Create a recovery user with a random password. mysqlshdk::mysql::Auth_options creds; { std::vector<std::string> hosts; hosts.push_back("%"); creds = mysqlshdk::gr::create_recovery_user("test_gr_user", *m_instance, hosts, {}); } // Check replication user (now it exist and it has no missing privileges). res = mysqlshdk::gr::check_replication_user(*m_instance, "test_gr_user", "%"); EXPECT_TRUE(res.user_exists()); EXPECT_EQ(std::set<std::string>{}, res.missing_privileges()); EXPECT_FALSE(res.has_missing_privileges()); EXPECT_FALSE(res.has_grant_option()); EXPECT_EQ(creds.user, "test_gr_user"); // it is expected a random password is generated when using an empty password // as parameter. EXPECT_EQ(true, creds.password.has_value()); // Drop user and recreate it with a given password { std::vector<std::string> hosts; hosts.push_back("%"); mysqlshdk::gr::Create_recovery_user_options options; options.password = "password"; creds = mysqlshdk::gr::create_recovery_user("test_gr_user", *m_instance, hosts, options); } // Check replication user (now it exist and it has no missing privileges). res = mysqlshdk::gr::check_replication_user(*m_instance, "test_gr_user", "%"); EXPECT_TRUE(res.user_exists()); EXPECT_EQ(std::set<std::string>{}, res.missing_privileges()); EXPECT_FALSE(res.has_missing_privileges()); EXPECT_FALSE(res.has_grant_option()); EXPECT_EQ(creds.user, "test_gr_user"); EXPECT_TRUE(static_cast<bool>(creds.password)); EXPECT_EQ(*creds.password, "password"); // Clean up (remove the create user at the end) m_instance->drop_user("test_gr_user", "%"); } TEST_F(Group_replication_test, start_stop_gr) { // Beside the start_group_replication() and stop_group_replication() // functions, the function get_member_state() is also // tested here since the test scenario is the same, in order to avoid // additional execution time to run similar test cases. // NOTE: START and STOP GROUP_REPLICATION is slow. using mysqlshdk::gr::Member_state; using mysqlshdk::mysql::Var_qualifier; // Check if used server meets the requirements. std::optional<int64_t> server_id = m_instance->get_sysvar_int("server_id"); if (*server_id == 0) { SKIP_TEST("Test server does not meet GR requirements: server_id is 0."); } bool log_bin = m_instance->get_sysvar_bool("log_bin", false); if (!log_bin) { SKIP_TEST("Test server does not meet GR requirements: log_bin must be ON."); } bool gtid_mode = m_instance->get_sysvar_bool("gtid_mode", false); if (!gtid_mode) { SKIP_TEST( "Test server does not meet GR requirements: gtid_mode must be ON."); } bool enforce_gtid_consistency = m_instance->get_sysvar_bool("enforce_gtid_consistency", false); if (!enforce_gtid_consistency) { SKIP_TEST( "Test server does not meet GR requirements: enforce_gtid_consistency " "must be ON."); } std::optional<std::string> master_info_repository = m_instance->get_sysvar_string("master_info_repository"); if ((*master_info_repository).compare("TABLE") != 0) { SKIP_TEST( "Test server does not meet GR requirements: master_info_repository " "must be 'TABLE'."); } std::optional<std::string> relay_log_info_repository = m_instance->get_sysvar_string("relay_log_info_repository"); if ((*relay_log_info_repository).compare("TABLE") != 0) { SKIP_TEST( "Test server does not meet GR requirements: relay_log_info_repository " "must be 'TABLE'."); } std::optional<std::string> binlog_checksum = m_instance->get_sysvar_string("binlog_checksum"); if ((*binlog_checksum).compare("NONE") != 0 && m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 21)) { SKIP_TEST( "Test server does not meet GR requirements: binlog_checksum must be " "'NONE'."); } bool log_slave_updates = m_instance->get_sysvar_bool("log_slave_updates", false); if (!log_slave_updates) { SKIP_TEST( "Test server does not meet GR requirements: log_slave_updates must " "be ON."); } std::optional<std::string> binlog_format = m_instance->get_sysvar_string("binlog_format"); if ((*binlog_format).compare("ROW") != 0) { SKIP_TEST( "Test server does not meet GR requirements: binlog_format must be " "'ROW'."); } // Test: member is not part of any group, state must be MISSING. Member_state state_res = mysqlshdk::gr::get_member_state(*m_instance); EXPECT_EQ(state_res, Member_state::MISSING); // Install GR plugin if needed. std::optional<std::string> init_plugin_state = m_instance->get_plugin_status(mysqlshdk::gr::k_gr_plugin_name); if (!init_plugin_state.has_value()) { mysqlshdk::gr::install_group_replication_plugin(*m_instance, nullptr); } // Get initial value of GR variables (to restore at the end). std::optional<std::string> gr_group_name = m_instance->get_sysvar_string("group_replication_group_name"); std::optional<std::string> gr_local_address = m_instance->get_sysvar_string("group_replication_local_address"); // Set GR variable to start GR. std::string group_name = mysqlshdk::gr::generate_uuid(*m_instance); m_instance->set_sysvar("group_replication_group_name", group_name, Var_qualifier::GLOBAL); std::string local_address = "localhost:13013"; m_instance->set_sysvar("group_replication_local_address", local_address, Var_qualifier::GLOBAL); // Test: Start Group Replication. mysqlshdk::gr::start_group_replication(*m_instance, true); // SUPER READ ONLY must be OFF (verify wait for it to be disable). bool read_only = m_instance->get_sysvar_bool("super_read_only", false); EXPECT_FALSE(read_only); // Test: member is part of GR group, state must be RECOVERING or ONLINE. state_res = mysqlshdk::gr::get_member_state(*m_instance); if (state_res == Member_state::ONLINE || state_res == Member_state::RECOVERING) SUCCEED(); else ADD_FAILURE() << "Unexpected status after starting GR, member state must " "be ONLINE or RECOVERING"; // Check GR server status (must be RECOVERING or ONLINE). auto session = m_instance->get_session(); std::string gr_status_stmt = "SELECT MEMBER_STATE " "FROM performance_schema.replication_group_members " "WHERE MEMBER_ID = @@server_uuid"; auto resultset = session->query(gr_status_stmt); auto row = resultset->fetch_one(); std::string status = row ? row->get_string(0) : "(empty)"; if (status.compare("ONLINE") != 0 && status.compare("RECOVERING")) ADD_FAILURE() << "Unexpected status after starting GR: " << status; // Test: Start Group Replication fails for group already running. EXPECT_THROW(mysqlshdk::gr::start_group_replication(*m_instance, true), std::exception); // Test: Stop Group Replication. mysqlshdk::gr::stop_group_replication(*m_instance); // Starting from MySQL 5.7.20 GR automatically enables super_read_only after // stop. Thus, always disable read_only ro consider this situation. m_instance->set_sysvar("super_read_only", false, Var_qualifier::GLOBAL); m_instance->set_sysvar("read_only", false, Var_qualifier::GLOBAL); // Test: member is still part of the group, but its state is OFFLINE. state_res = mysqlshdk::gr::get_member_state(*m_instance); EXPECT_EQ(state_res, Member_state::OFFLINE); // Clean up (restore initial server state). if (!gr_group_name->empty()) // NOTE: The group_name cannot be set with an empty value. m_instance->set_sysvar("group_replication_group_name", *gr_group_name, Var_qualifier::GLOBAL); m_instance->set_sysvar("group_replication_local_address", *gr_local_address, Var_qualifier::GLOBAL); if (!init_plugin_state.has_value()) { mysqlshdk::gr::uninstall_group_replication_plugin(*m_instance, nullptr); } } TEST_F(Group_replication_test, members_state) { using mysqlshdk::gr::Member_state; // Test to_string() function SCOPED_TRACE("to_string() function test"); std::string str_res = mysqlshdk::gr::to_string(Member_state::ONLINE); EXPECT_STREQ("ONLINE", str_res.c_str()); str_res = mysqlshdk::gr::to_string(Member_state::RECOVERING); EXPECT_STREQ("RECOVERING", str_res.c_str()); str_res = mysqlshdk::gr::to_string(Member_state::OFFLINE); EXPECT_STREQ("OFFLINE", str_res.c_str()); str_res = mysqlshdk::gr::to_string(Member_state::ERROR); EXPECT_STREQ("ERROR", str_res.c_str()); str_res = mysqlshdk::gr::to_string(Member_state::UNREACHABLE); EXPECT_STREQ("UNREACHABLE", str_res.c_str()); str_res = mysqlshdk::gr::to_string(Member_state::MISSING); EXPECT_STREQ("(MISSING)", str_res.c_str()); // Test to_member_state() function SCOPED_TRACE("to_member_state() function test"); Member_state state_res = mysqlshdk::gr::to_member_state("ONLINE"); EXPECT_EQ(state_res, Member_state::ONLINE); state_res = mysqlshdk::gr::to_member_state("RECOVERING"); EXPECT_EQ(state_res, Member_state::RECOVERING); state_res = mysqlshdk::gr::to_member_state("Offline"); EXPECT_EQ(state_res, Member_state::OFFLINE); state_res = mysqlshdk::gr::to_member_state("error"); EXPECT_EQ(state_res, Member_state::ERROR); state_res = mysqlshdk::gr::to_member_state("uNREACHABLE"); EXPECT_EQ(state_res, Member_state::UNREACHABLE); state_res = mysqlshdk::gr::to_member_state("MISSING"); EXPECT_EQ(state_res, Member_state::MISSING); state_res = mysqlshdk::gr::to_member_state("(MISSING)"); EXPECT_EQ(state_res, Member_state::MISSING); EXPECT_THROW(mysqlshdk::gr::to_member_state("invalid"), std::runtime_error); } TEST_F(Group_replication_test, is_group_replication_delayed_starting) { using mysqlshdk::db::Type; std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; mock_session ->expect_query( "SELECT COUNT(*) FROM performance_schema.threads WHERE NAME = " "'thread/group_rpl/THD_delayed_initialization'") .then_return({{"", {"COUNT(*)"}, {Type::UInteger}, {{"1"}}}}); EXPECT_TRUE(mysqlshdk::gr::is_group_replication_delayed_starting(instance)); mock_session ->expect_query( "SELECT COUNT(*) FROM performance_schema.threads WHERE NAME = " "'thread/group_rpl/THD_delayed_initialization'") .then_return({{"", {"COUNT(*)"}, {Type::UInteger}, {{"0"}}}}); EXPECT_FALSE(mysqlshdk::gr::is_group_replication_delayed_starting(instance)); } TEST_F(Group_replication_test, is_running_gr_auto_rejoin) { using mysqlshdk::db::Type; std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; mock_session ->expect_query( "SELECT PROCESSLIST_STATE FROM performance_schema.threads WHERE NAME " "= 'thread/group_rpl/THD_autorejoin'") .then_return({{"", {"PROCESSLIST_STATE"}, {Type::String}, {{"Undergoing auto-rejoin procedure"}}}}); EXPECT_TRUE(mysqlshdk::gr::is_running_gr_auto_rejoin(instance)); mock_session ->expect_query( "SELECT PROCESSLIST_STATE FROM performance_schema.threads WHERE NAME " "= 'thread/group_rpl/THD_autorejoin'") .then_return({{"", {"PROCESSLIST_STATE"}, {Type::String}, {}}}); EXPECT_FALSE(mysqlshdk::gr::is_running_gr_auto_rejoin(instance)); } TEST_F(Group_replication_test, get_all_configurations) { std::map<std::string, std::optional<std::string>> res = mysqlshdk::gr::get_all_configurations(*m_instance); // NOTE: Only auto_increment variables are returned if GR is not configured. // Check only those values to avoid non-deterministic issues, depending // on the server version and/or configuration (e.g., new // group_replication_consistency variable added for 8.0.14 servers). std::vector<std::string> vars; std::vector<std::string> values; for (const auto &[name, val] : res) { vars.push_back(name); values.push_back(*val); } EXPECT_THAT(vars, Contains("auto_increment_increment")); EXPECT_THAT(vars, Contains("auto_increment_offset")); } TEST_F(Group_replication_test, check_log_bin_compatibility_disabled_57) { using mysqlshdk::mysql::Config_type; using mysqlshdk::mysql::Config_types; using mysqlshdk::mysql::Invalid_config; using mysqlshdk::mysql::Var_qualifier; using namespace std::literals; mysqlshdk::mysql::Mock_instance inst; std::vector<Invalid_config> res; // Test everything assuming 5.7, with binlog default OFF and no SET PERSIST EXPECT_CALL(inst, get_sysvar_bool("log_bin"sv, Var_qualifier::GLOBAL)) .WillRepeatedly(Return(std::optional<bool>(false))); EXPECT_CALL(inst, get_sysvar_string("log_bin"sv, Var_qualifier::GLOBAL)) .WillRepeatedly(Return(std::optional<std::string>("OFF"))); EXPECT_CALL(inst, get_version()) .WillRepeatedly(Return(mysqlshdk::utils::Version(5, 7, 24))); // Create config object (only with a server handler). mysqlshdk::config::Config cfg; cfg.add_handler(mysqlshdk::config::k_dft_cfg_server_handler, std::make_unique<mysqlshdk::config::Config_server_handler>( &inst, Var_qualifier::GLOBAL)); mysqlshdk::mysql::check_log_bin_compatibility(inst, cfg, &res); // should have 3 issues in 5.7, since binary log is disabled. ASSERT_EQ(3, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); EXPECT_STREQ(res.at(1).var_name.c_str(), "skip_log_bin"); EXPECT_STREQ(res.at(1).current_val.c_str(), "<not set>"); EXPECT_STREQ(res.at(1).required_val.c_str(), "<not set>"); EXPECT_EQ(res.at(1).types, Config_type::CONFIG); EXPECT_EQ(res.at(1).restart, true); EXPECT_STREQ(res.at(2).var_name.c_str(), "disable_log_bin"); EXPECT_STREQ(res.at(2).current_val.c_str(), "<not set>"); EXPECT_STREQ(res.at(2).required_val.c_str(), "<not set>"); EXPECT_EQ(res.at(2).types, Config_type::CONFIG); EXPECT_EQ(res.at(2).restart, true); // Create an empty test option file and add the option file config handler. create_file(m_cfg_path, ""); cfg.add_handler(mysqlshdk::config::k_dft_cfg_file_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( new mysqlshdk::config::Config_file_handler( "uuid1", m_cfg_path, m_cfg_path))); // Issues found on both server and option file (with no values set). res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(inst, cfg, &res); ASSERT_EQ(1, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); // Set incompatible values on file, issues found. cfg.set_for_handler("skip_log_bin", std::optional<std::string>(), mysqlshdk::config::k_dft_cfg_file_handler); cfg.set_for_handler("disable_log_bin", std::optional<std::string>(), mysqlshdk::config::k_dft_cfg_file_handler); cfg.apply(); res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(inst, cfg, &res); ASSERT_EQ(3, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); EXPECT_STREQ(res.at(1).var_name.c_str(), "disable_log_bin"); EXPECT_STREQ(res.at(1).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(1).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(1).types, Config_type::CONFIG); EXPECT_EQ(res.at(1).restart, true); EXPECT_STREQ(res.at(2).var_name.c_str(), "skip_log_bin"); EXPECT_STREQ(res.at(2).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(2).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(2).types, Config_type::CONFIG); EXPECT_EQ(res.at(2).restart, true); // Delete the config file. shcore::delete_file(m_cfg_path, true); } TEST_F(Group_replication_test, check_log_bin_compatibility_disabled_80) { using mysqlshdk::mysql::Config_type; using mysqlshdk::mysql::Config_types; using mysqlshdk::mysql::Invalid_config; using mysqlshdk::mysql::Var_qualifier; using namespace std::literals; mysqlshdk::mysql::Mock_instance inst; std::vector<Invalid_config> res; // With 8.0, "log_bin" is read-only and cannot be persisted. The only way to // disable it is with skip_log_bin or disable_log_bin EXPECT_CALL(inst, get_sysvar_bool("log_bin"sv, Var_qualifier::GLOBAL)) .WillRepeatedly(Return(std::optional<bool>(false))); EXPECT_CALL(inst, get_sysvar_string("log_bin"sv, Var_qualifier::GLOBAL)) .WillRepeatedly(Return(std::optional<std::string>("OFF"))); EXPECT_CALL(inst, get_version()) .WillRepeatedly(Return(mysqlshdk::utils::Version(8, 0, 3))); // Create config object (only with a server handler). mysqlshdk::config::Config cfg; cfg.add_handler(mysqlshdk::config::k_dft_cfg_server_handler, std::make_unique<mysqlshdk::config::Config_server_handler>( &inst, Var_qualifier::GLOBAL)); mysqlshdk::mysql::check_log_bin_compatibility(inst, cfg, &res); EXPECT_STREQ(res.at(0).var_name.c_str(), "skip_log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); EXPECT_STREQ(res.at(1).var_name.c_str(), "disable_log_bin"); EXPECT_STREQ(res.at(1).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(1).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(1).types, Config_type::CONFIG); EXPECT_EQ(res.at(1).restart, true); // Create an empty test option file and add the option file config handler. create_file(m_cfg_path, ""); cfg.add_handler(mysqlshdk::config::k_dft_cfg_file_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( new mysqlshdk::config::Config_file_handler( "uuid1", m_cfg_path, m_cfg_path))); // Issues found on both server and option file (with no values set). res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(inst, cfg, &res); // if server version is >=8.0.3 then the log_bin is enabled by default so // there is no need to be the log_bin option on the file ASSERT_EQ(1, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), "OFF"); EXPECT_STREQ(res.at(0).required_val.c_str(), "ON"); EXPECT_EQ(res.at(0).types, Config_type::RESTART_ONLY); EXPECT_EQ(res.at(0).restart, true); // Set incompatible values on file, issues found. cfg.set_for_handler("skip_log_bin", std::optional<std::string>(), mysqlshdk::config::k_dft_cfg_file_handler); cfg.set_for_handler("disable_log_bin", std::optional<std::string>(), mysqlshdk::config::k_dft_cfg_file_handler); cfg.apply(); res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(inst, cfg, &res); ASSERT_EQ(2, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "disable_log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); EXPECT_STREQ(res.at(1).var_name.c_str(), "skip_log_bin"); EXPECT_STREQ(res.at(1).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(1).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(1).types, Config_type::CONFIG); EXPECT_EQ(res.at(1).restart, true); // Delete the config file. shcore::delete_file(m_cfg_path, true); } TEST_F(Group_replication_test, check_log_bin_compatibility_enabled) { using mysqlshdk::mysql::Config_type; using mysqlshdk::mysql::Config_types; using mysqlshdk::mysql::Invalid_config; using mysqlshdk::mysql::Var_qualifier; bool log_bin = m_instance->get_sysvar_bool("log_bin", false); if (!log_bin) { SKIP_TEST( "Test server does not meet GR requirements: binary_log is disabled and " "should be enabled.") } // Create config object (only with a server handler). mysqlshdk::config::Config cfg; cfg.add_handler( mysqlshdk::config::k_dft_cfg_server_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( std::make_unique<mysqlshdk::config::Config_server_handler>( m_instance, Var_qualifier::GLOBAL))); // should have no issues, since binary log is enabled. std::vector<Invalid_config> res; mysqlshdk::mysql::check_log_bin_compatibility(*m_instance, cfg, &res); EXPECT_EQ(0, res.size()); // Create an empty test option file and add the option file config handler. create_file(m_cfg_path, ""); cfg.add_handler(mysqlshdk::config::k_dft_cfg_file_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( new mysqlshdk::config::Config_file_handler( "uuid1", m_cfg_path, m_cfg_path))); // Issues found on the option file (with no values set). res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(*m_instance, cfg, &res); // if server version is >=8.0.3 then the log_bin is enabled by default so // there is no need to be the log_bin option on the file if (m_instance->get_version() >= mysqlshdk::utils::Version(8, 0, 3)) { ASSERT_EQ(0, res.size()); } else { ASSERT_EQ(1, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); } // Set incompatible values on file, issues found. cfg.set_for_handler("skip_log_bin", std::optional<std::string>(), mysqlshdk::config::k_dft_cfg_file_handler); cfg.set_for_handler("disable_log_bin", std::optional<std::string>(), mysqlshdk::config::k_dft_cfg_file_handler); cfg.apply(); res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(*m_instance, cfg, &res); if (m_instance->get_version() >= mysqlshdk::utils::Version(8, 0, 3)) { ASSERT_EQ(2, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "disable_log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); EXPECT_STREQ(res.at(1).var_name.c_str(), "skip_log_bin"); EXPECT_STREQ(res.at(1).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(1).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(1).types, Config_type::CONFIG); EXPECT_EQ(res.at(1).restart, true); } else { ASSERT_EQ(3, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "log_bin"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(0).required_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, true); EXPECT_STREQ(res.at(1).var_name.c_str(), "disable_log_bin"); EXPECT_STREQ(res.at(1).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(1).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(1).types, Config_type::CONFIG); EXPECT_EQ(res.at(1).restart, true); EXPECT_STREQ(res.at(2).var_name.c_str(), "skip_log_bin"); EXPECT_STREQ(res.at(2).current_val.c_str(), mysqlshdk::mysql::k_no_value); EXPECT_STREQ(res.at(2).required_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(2).types, Config_type::CONFIG); EXPECT_EQ(res.at(2).restart, true); } // Set compatible values on file, no issues found. // Get the config file handler reference (to remove option from config file). mysqlshdk::config::Config_file_handler *file_cfg_handler = static_cast<mysqlshdk::config::Config_file_handler *>( cfg.get_handler(mysqlshdk::config::k_dft_cfg_file_handler)); file_cfg_handler->remove("skip_log_bin"); file_cfg_handler->remove("disable_log_bin"); if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 3)) { file_cfg_handler->set("log_bin", std::optional<std::string>()); } cfg.apply(); res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(*m_instance, cfg, &res); EXPECT_EQ(0, res.size()); cfg.set_for_handler("log_bin", std::optional<std::string>("Some text"), mysqlshdk::config::k_dft_cfg_file_handler); cfg.apply(); res.clear(); mysqlshdk::mysql::check_log_bin_compatibility(*m_instance, cfg, &res); EXPECT_EQ(0, res.size()); // Delete the config file. shcore::delete_file(m_cfg_path, true); } TEST_F(Group_replication_test, check_server_id_compatibility) { using mysqlshdk::mysql::Config_type; using mysqlshdk::mysql::Config_types; using mysqlshdk::mysql::Invalid_config; using mysqlshdk::mysql::Var_qualifier; using mysqlshdk::utils::Version; // Get current server_id variable value to restore at the end. std::optional<int64_t> cur_server_id = m_instance->get_sysvar_int("server_id", Var_qualifier::GLOBAL); // Create config object (only with a server handler). mysqlshdk::config::Config cfg; cfg.add_handler( mysqlshdk::config::k_dft_cfg_server_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( std::make_unique<mysqlshdk::config::Config_server_handler>( m_instance, Var_qualifier::GLOBAL))); std::vector<Invalid_config> res; // If server_version >= 8.0.3 and the server_id is the default compiled // value, there should be an issue reported. if (m_instance->get_version() >= mysqlshdk::utils::Version(8, 0, 3) && m_instance->has_variable_compiled_value("server_id")) { mysqlshdk::mysql::check_server_id_compatibility(*m_instance, cfg, &res); ASSERT_EQ(1, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "server_id"); EXPECT_STREQ(res.at(0).required_val.c_str(), "<unique ID>"); EXPECT_EQ(res.at(0).types, Config_type::SERVER); EXPECT_EQ(res.at(0).restart, true); } // No issues reported if server_id != 0 and has been changed. m_instance->set_sysvar("server_id", static_cast<int64_t>(1), Var_qualifier::GLOBAL); res.clear(); mysqlshdk::mysql::check_server_id_compatibility(*m_instance, cfg, &res); EXPECT_EQ(0, res.size()); // Set server_id=0, and issues will be found m_instance->set_sysvar("server_id", static_cast<int64_t>(0), Var_qualifier::GLOBAL); res.clear(); mysqlshdk::mysql::check_server_id_compatibility(*m_instance, cfg, &res); ASSERT_EQ(1, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "server_id"); EXPECT_STREQ(res.at(0).current_val.c_str(), "0"); EXPECT_STREQ(res.at(0).required_val.c_str(), "<unique ID>"); EXPECT_EQ(res.at(0).types, Config_type::SERVER); EXPECT_EQ(res.at(0).restart, true); // Create an empty test option file and add the option file config handler. create_file(m_cfg_path, ""); cfg.add_handler(mysqlshdk::config::k_dft_cfg_file_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( new mysqlshdk::config::Config_file_handler( "uuid1", m_cfg_path, m_cfg_path))); // Issues found on the option file (with no values set). // The current value should be the one from the server. res.clear(); mysqlshdk::mysql::check_server_id_compatibility(*m_instance, cfg, &res); ASSERT_EQ(1, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "server_id"); EXPECT_STREQ(res.at(0).current_val.c_str(), "0"); EXPECT_STREQ(res.at(0).required_val.c_str(), "<unique ID>"); EXPECT_TRUE(res.at(0).types.is_set(Config_type::CONFIG)); EXPECT_TRUE(res.at(0).types.is_set(Config_type::SERVER)); EXPECT_EQ(res.at(0).restart, true); // Fixing the value on the server will still leave the warning for the empty // config file m_instance->set_sysvar("server_id", static_cast<int64_t>(1), Var_qualifier::GLOBAL); res.clear(); mysqlshdk::mysql::check_server_id_compatibility(*m_instance, cfg, &res); ASSERT_EQ(1, res.size()); EXPECT_STREQ(res.at(0).var_name.c_str(), "server_id"); EXPECT_STREQ(res.at(0).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(0).required_val.c_str(), "<unique ID>"); EXPECT_EQ(res.at(0).types, Config_type::CONFIG); EXPECT_EQ(res.at(0).restart, false); // Fixing the value on the config will clear all warnings. cfg.set_for_handler("server_id", std::optional<std::string>("1"), mysqlshdk::config::k_dft_cfg_file_handler); cfg.apply(); res.clear(); mysqlshdk::mysql::check_server_id_compatibility(*m_instance, cfg, &res); EXPECT_EQ(0, res.size()); // Delete the config file. shcore::delete_file(m_cfg_path, true); // Restore initial values. m_instance->set_sysvar("server_id", *cur_server_id, Var_qualifier::GLOBAL); } TEST_F(Group_replication_test, check_server_variables_compatibility) { using mysqlshdk::mysql::Config_type; using mysqlshdk::mysql::Config_types; using mysqlshdk::mysql::Invalid_config; using mysqlshdk::mysql::Var_qualifier; using mysqlshdk::utils::Version; std::string instance_port = std::to_string(m_instance->get_connection_options().get_port()); // we will just modify and test the dynamic server variables as well as // one of the non dynamic variables from the server. Testing all the non // dynamic variables would be impossible as we do not control the state of // the instance that is provided and we don't want to restart it. // Get the values of the dynamic variables. std::optional<std::string> cur_binlog_format = m_instance->get_sysvar_string("binlog_format", Var_qualifier::GLOBAL); std::optional<std::string> cur_binlog_checksum = m_instance->get_sysvar_string("binlog_checksum", Var_qualifier::GLOBAL); // now get the value of one of the other variables // Note, picked this one because this is the first returned variable on the // vector of invalid configs after the two dynamic variables, so if both // have correct values, this will be the invalid config at index 0. std::optional<std::string> cur_log_slave_updates = m_instance->get_sysvar_string("log_slave_updates", Var_qualifier::GLOBAL); bool log_slave_updates_correct = (*cur_log_slave_updates == "1") || (*cur_log_slave_updates == "ON"); bool binlog_checksum_allowed = m_instance->get_version() >= mysqlshdk::utils::Version(8, 0, 21); // Create config object (only with a server handler). mysqlshdk::config::Config cfg; cfg.add_handler( mysqlshdk::config::k_dft_cfg_server_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( std::make_unique<mysqlshdk::config::Config_server_handler>( m_instance, Var_qualifier::GLOBAL))); // change the dynamic variables so there are no server issues m_instance->set_sysvar("binlog_format", static_cast<std::string>("ROW"), Var_qualifier::GLOBAL); if (!binlog_checksum_allowed) { // binlog_checksum is only required to be NONE on server versions // below 8.0.21 m_instance->set_sysvar("binlog_checksum", static_cast<std::string>("NONE"), Var_qualifier::GLOBAL); } else { m_instance->set_sysvar("binlog_checksum", static_cast<std::string>("CRC32"), Var_qualifier::GLOBAL); } std::vector<Invalid_config> res; mysqlshdk::mysql::check_server_variables_compatibility(*m_instance, cfg, true, &res); if (!log_slave_updates_correct) { // if the log_slave_updates is not correct, the issues list must at least // have the log_slave_updates invalid config ASSERT_GE(res.size(), 1); auto it = std::find_if(res.begin(), res.end(), [](const Invalid_config &ic) { return ic.var_name == "log_slave_updates"; }); ASSERT_TRUE(it != res.end()); EXPECT_EQ(it->current_val, *cur_log_slave_updates); EXPECT_STREQ(it->required_val.c_str(), "ON"); EXPECT_EQ(it->types, Config_type::SERVER); EXPECT_EQ(it->restart, true); } // Create an empty test option file and add the option file config handler. create_file(m_cfg_path, ""); mysqlshdk::config::Config cfg_file_only; cfg_file_only.add_handler(mysqlshdk::config::k_dft_cfg_file_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( new mysqlshdk::config::Config_file_handler( "uuid1", m_cfg_path, m_cfg_path))); // Issues found on the option file only (with no values set). res.clear(); auto find = [&res](const std::string &opt) { size_t i = 0; for (const auto &r : res) { if (r.var_name == opt) return i; ++i; } ADD_FAILURE() << "Couldn't find " << opt; return i; }; mysqlshdk::mysql::check_server_variables_compatibility( *m_instance, cfg_file_only, true, &res); // if the config file is empty, there should be an issue for each of the // tested variables size_t i = 0; bool parallel_appliers_required = m_instance->get_version() >= mysqlshdk::utils::Version(8, 0, 23); if (!binlog_checksum_allowed) { if (parallel_appliers_required) { ASSERT_EQ(11, res.size()); } else { ASSERT_EQ(8, res.size()); } i = find("binlog_checksum"); EXPECT_STREQ(res.at(i).var_name.c_str(), "binlog_checksum"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "NONE"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); } else { if (!parallel_appliers_required) { ASSERT_EQ(6, res.size()); } else { ASSERT_EQ(7, res.size()); } } i = find("binlog_format"); EXPECT_STREQ(res.at(i).var_name.c_str(), "binlog_format"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "ROW"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 3)) { i = find("log_slave_updates"); EXPECT_STREQ(res.at(i).var_name.c_str(), "log_slave_updates"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "ON"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); } i = find("enforce_gtid_consistency"); EXPECT_STREQ(res.at(i).var_name.c_str(), "enforce_gtid_consistency"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "ON"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); i = find("gtid_mode"); EXPECT_STREQ(res.at(i).var_name.c_str(), "gtid_mode"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "ON"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 23)) { i = find("master_info_repository"); EXPECT_STREQ(res.at(i).var_name.c_str(), "master_info_repository"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "TABLE"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); i = find("relay_log_info_repository"); EXPECT_STREQ(res.at(i).var_name.c_str(), "relay_log_info_repository"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "TABLE"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); } i = find("transaction_write_set_extraction"); EXPECT_STREQ(res.at(i).var_name.c_str(), "transaction_write_set_extraction"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "XXHASH64"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); if (parallel_appliers_required) { i = find("binlog_transaction_dependency_tracking"); EXPECT_STREQ(res.at(i).var_name.c_str(), "binlog_transaction_dependency_tracking"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "WRITESET"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); i = find(mysqlshdk::mysql::get_replication_option_keyword( m_instance->get_version(), "slave_parallel_type")); EXPECT_STREQ(res.at(i).var_name.c_str(), mysqlshdk::mysql::get_replication_option_keyword( m_instance->get_version(), "slave_parallel_type") .c_str()); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "LOGICAL_CLOCK"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); i = find(mysqlshdk::mysql::get_replication_option_keyword( m_instance->get_version(), "slave_preserve_commit_order")); EXPECT_STREQ(res.at(i).var_name.c_str(), mysqlshdk::mysql::get_replication_option_keyword( m_instance->get_version(), "slave_preserve_commit_order") .c_str()); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "ON"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); } // add the empty file as well to the cfg handler and check that incorrect // server results override the incorrect file results for the current value // field of the invalid config. cfg.add_handler(mysqlshdk::config::k_dft_cfg_file_handler, std::unique_ptr<mysqlshdk::config::IConfig_handler>( new mysqlshdk::config::Config_file_handler( "uuid1", m_cfg_path, m_cfg_path))); // change the dynamic variables so there are server issues m_instance->set_sysvar("binlog_format", static_cast<std::string>("STATEMENT"), Var_qualifier::GLOBAL); m_instance->set_sysvar("binlog_checksum", static_cast<std::string>("CRC32"), Var_qualifier::GLOBAL); // Issues found on the option file only (with no values set). res.clear(); mysqlshdk::mysql::check_server_variables_compatibility(*m_instance, cfg, true, &res); // since all the file configurations are wrong, we know that even if some // variables have correct results on the server, they will still have a // invalid config. Since the cfg has a server handler, then it should also // have one more entry for the report_port option. if (!binlog_checksum_allowed) { if (parallel_appliers_required) { ASSERT_EQ(12, res.size()); } else { ASSERT_EQ(9, res.size()); } i = find("binlog_checksum"); EXPECT_STREQ(res.at(i).var_name.c_str(), "binlog_checksum"); EXPECT_STREQ(res.at(i).current_val.c_str(), "CRC32"); EXPECT_STREQ(res.at(i).required_val.c_str(), "NONE"); EXPECT_TRUE(res.at(i).types.is_set(Config_type::CONFIG)); EXPECT_TRUE(res.at(i).types.is_set(Config_type::SERVER)); EXPECT_EQ(res.at(i).restart, false); } else { if (parallel_appliers_required) { ASSERT_EQ(8, res.size()); } else { ASSERT_EQ(7, res.size()); } } i = find("binlog_format"); EXPECT_STREQ(res.at(i).var_name.c_str(), "binlog_format"); EXPECT_STREQ(res.at(i).current_val.c_str(), "STATEMENT"); EXPECT_STREQ(res.at(i).required_val.c_str(), "ROW"); EXPECT_TRUE(res.at(i).types.is_set(Config_type::CONFIG)); EXPECT_TRUE(res.at(i).types.is_set(Config_type::SERVER)); EXPECT_EQ(res.at(i).restart, false); if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 3)) { i = find("log_slave_updates"); if (log_slave_updates_correct) { EXPECT_STREQ(res.at(i).var_name.c_str(), "log_slave_updates"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_STREQ(res.at(i).required_val.c_str(), "ON"); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); } else { EXPECT_STREQ(res.at(i).var_name.c_str(), "log_slave_updates"); EXPECT_EQ(res.at(i).current_val, *cur_log_slave_updates); EXPECT_STREQ(res.at(i).required_val.c_str(), "ON"); EXPECT_TRUE(res.at(i).types.is_set(Config_type::CONFIG)); EXPECT_TRUE(res.at(i).types.is_set(Config_type::SERVER)); EXPECT_EQ(res.at(i).restart, true); } } i = find("report_port"); EXPECT_STREQ(res.at(i).var_name.c_str(), "report_port"); EXPECT_STREQ(res.at(i).current_val.c_str(), mysqlshdk::mysql::k_value_not_set); EXPECT_EQ(res.at(i).required_val, instance_port); EXPECT_EQ(res.at(i).types, Config_type::CONFIG); EXPECT_EQ(res.at(i).restart, false); // Fixing all the config file incorrect values on both config objects cfg_file_only.set_for_handler("binlog_format", std::optional<std::string>("ROW"), mysqlshdk::config::k_dft_cfg_file_handler); if (!binlog_checksum_allowed) { cfg_file_only.set_for_handler("binlog_checksum", std::optional<std::string>("NONE"), mysqlshdk::config::k_dft_cfg_file_handler); } if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 3)) { cfg_file_only.set_for_handler("log_slave_updates", std::optional<std::string>("ON"), mysqlshdk::config::k_dft_cfg_file_handler); } cfg_file_only.set_for_handler("enforce_gtid_consistency", std::optional<std::string>("ON"), mysqlshdk::config::k_dft_cfg_file_handler); cfg_file_only.set_for_handler("gtid_mode", std::optional<std::string>("ON"), mysqlshdk::config::k_dft_cfg_file_handler); if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 23)) { cfg_file_only.set_for_handler("master_info_repository", std::optional<std::string>("TABLE"), mysqlshdk::config::k_dft_cfg_file_handler); cfg_file_only.set_for_handler("relay_log_info_repository", std::optional<std::string>("TABLE"), mysqlshdk::config::k_dft_cfg_file_handler); } cfg_file_only.set_for_handler( "transaction_write_set_extraction", std::optional<std::string>("MURMUR32"), // different but valid mysqlshdk::config::k_dft_cfg_file_handler); cfg_file_only.set_for_handler("report_port", std::optional<std::string>(instance_port), mysqlshdk::config::k_dft_cfg_file_handler); if (parallel_appliers_required) { cfg_file_only.set_for_handler("binlog_transaction_dependency_tracking", std::optional<std::string>("WRITESET"), mysqlshdk::config::k_dft_cfg_file_handler); cfg_file_only.set_for_handler( mysqlshdk::mysql::get_replication_option_keyword( m_instance->get_version(), "slave_parallel_type"), std::optional<std::string>("LOGICAL_CLOCK"), mysqlshdk::config::k_dft_cfg_file_handler); cfg_file_only.set_for_handler( mysqlshdk::mysql::get_replication_option_keyword( m_instance->get_version(), "slave_preserve_commit_order"), std::optional<std::string>("ON"), mysqlshdk::config::k_dft_cfg_file_handler); } cfg_file_only.apply(); cfg.set_for_handler("binlog_format", std::optional<std::string>("ROW"), mysqlshdk::config::k_dft_cfg_file_handler); if (!binlog_checksum_allowed) { cfg.set_for_handler("binlog_checksum", std::optional<std::string>("NONE"), mysqlshdk::config::k_dft_cfg_file_handler); } if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 3)) { cfg.set_for_handler("log_slave_updates", std::optional<std::string>("ON"), mysqlshdk::config::k_dft_cfg_file_handler); } cfg.set_for_handler("enforce_gtid_consistency", std::optional<std::string>("ON"), mysqlshdk::config::k_dft_cfg_file_handler); cfg.set_for_handler("gtid_mode", std::optional<std::string>("ON"), mysqlshdk::config::k_dft_cfg_file_handler); if (m_instance->get_version() < mysqlshdk::utils::Version(8, 0, 23)) { cfg.set_for_handler("master_info_repository", std::optional<std::string>("TABLE"), mysqlshdk::config::k_dft_cfg_file_handler); cfg.set_for_handler("relay_log_info_repository", std::optional<std::string>("TABLE"), mysqlshdk::config::k_dft_cfg_file_handler); } cfg.set_for_handler( "transaction_write_set_extraction", std::optional<std::string>("MURMUR32"), // different but valid mysqlshdk::config::k_dft_cfg_file_handler); cfg.set_for_handler("report_port", std::optional<std::string>(instance_port), mysqlshdk::config::k_dft_cfg_file_handler); cfg.apply(); // Since all incorrect values have been fixed on the configuration file // and the cfg_file_only config object only had a config_file handler then it // should have no issues res.clear(); mysqlshdk::mysql::check_server_variables_compatibility( *m_instance, cfg_file_only, true, &res); EXPECT_EQ(0, res.size()); // Since all incorrect values have been fixed on the configuration file // but the cfg config object also had a server handler, then it should at // least have the issues that are still present on the server. res.clear(); mysqlshdk::mysql::check_server_variables_compatibility(*m_instance, cfg, true, &res); if (!log_slave_updates_correct) { ASSERT_GE(res.size(), 3); i = find("log_slave_updates"); EXPECT_STREQ(res.at(i).var_name.c_str(), "log_slave_updates"); EXPECT_EQ(res.at(i).current_val, *cur_log_slave_updates); EXPECT_STREQ(res.at(i).required_val.c_str(), "ON"); EXPECT_EQ(res.at(i).types, Config_type::SERVER); EXPECT_EQ(res.at(i).restart, true); } i = find("binlog_format"); EXPECT_STREQ(res.at(i).var_name.c_str(), "binlog_format"); EXPECT_STREQ(res.at(i).current_val.c_str(), "STATEMENT"); EXPECT_STREQ(res.at(i).required_val.c_str(), "ROW"); EXPECT_EQ(res.at(i).types, Config_type::SERVER); EXPECT_EQ(res.at(i).restart, false); if (!binlog_checksum_allowed) { i = find("binlog_checksum"); EXPECT_STREQ(res.at(i).var_name.c_str(), "binlog_checksum"); EXPECT_STREQ(res.at(i).current_val.c_str(), "CRC32"); EXPECT_STREQ(res.at(i).required_val.c_str(), "NONE"); EXPECT_EQ(res.at(i).types, Config_type::SERVER); EXPECT_EQ(res.at(i).restart, false); } // Delete the config file. shcore::delete_file(m_cfg_path, true); // Restore initial values. m_instance->set_sysvar("binlog_format", *cur_binlog_format, Var_qualifier::GLOBAL); m_instance->set_sysvar("binlog_checksum", *cur_binlog_checksum, Var_qualifier::GLOBAL); } TEST_F(Group_replication_test, is_active_member) { using mysqlshdk::db::Type; std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; mock_session ->expect_query( "SELECT count(*) " "FROM performance_schema.replication_group_members " "WHERE MEMBER_HOST = 'localhost' AND MEMBER_PORT = 3306 " "AND MEMBER_STATE NOT IN ('OFFLINE', 'UNREACHABLE')") .then_return({{"", {"count(*)"}, {Type::String}, {{"0"}}}}); mock_session->expect_query( {"SELECT COALESCE(@@report_host, @@hostname), " "COALESCE(@@report_port, @@port)", {"Host", "Port"}, {mysqlshdk::db::Type::String, mysqlshdk::db::Type::Integer}, {{"mock@localhost", "3306"}}}); EXPECT_FALSE(mysqlshdk::gr::is_active_member(instance, "localhost", 3306)); mock_session ->expect_query( "SELECT count(*) " "FROM performance_schema.replication_group_members " "WHERE MEMBER_HOST = 'localhost' AND MEMBER_PORT = 3306 " "AND MEMBER_STATE NOT IN ('OFFLINE', 'UNREACHABLE')") .then_return({{"", {"count(*)"}, {Type::String}, {{"1"}}}}); mock_session->expect_query( {"SELECT COALESCE(@@report_host, @@hostname), " "COALESCE(@@report_port, @@port)", {"Host", "Port"}, {mysqlshdk::db::Type::String, mysqlshdk::db::Type::Integer}, {{"mock@localhost", "3306"}}}); EXPECT_TRUE(mysqlshdk::gr::is_active_member(instance, "localhost", 3306)); } TEST_F(Group_replication_test, update_auto_increment) { using mysqlshdk::db::Type; using mysqlshdk::gr::Topology_mode; using mysqlshdk::mysql::Var_qualifier; std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; // Create config objects with 3 server handlers. mysqlshdk::config::Config cfg_global; for (int i = 0; i < 3; i++) { cfg_global.add_handler( "instance_" + std::to_string(i), std::unique_ptr<mysqlshdk::config::IConfig_handler>( std::make_unique<mysqlshdk::config::Config_server_handler>( &instance, Var_qualifier::GLOBAL))); } // Set auto-increment for single-primary (3 instances). for (int i = 0; i < 3; i++) { mock_session->expect_query("SET GLOBAL `auto_increment_increment` = 1") .then({""}); mock_session->expect_query("SET GLOBAL `auto_increment_offset` = 2") .then({""}); } mysqlshdk::gr::update_auto_increment(&cfg_global, Topology_mode::SINGLE_PRIMARY); cfg_global.apply(); // Create config objects with 10 server handlers with PERSIST support. mysqlshdk::config::Config cfg_persist; for (int i = 0; i < 10; i++) { cfg_persist.add_handler( "instance_" + std::to_string(i), std::unique_ptr<mysqlshdk::config::IConfig_handler>( std::make_unique<mysqlshdk::config::Config_server_handler>( &instance, Var_qualifier::PERSIST))); } // Set auto-increment for single-primary (10 instances with PERSIST support). for (int i = 0; i < 10; i++) { mock_session->expect_query("SET PERSIST `auto_increment_increment` = 1") .then({""}); mock_session->expect_query("SET PERSIST `auto_increment_offset` = 2") .then({""}); } mysqlshdk::gr::update_auto_increment(&cfg_persist, Topology_mode::SINGLE_PRIMARY); cfg_persist.apply(); // Remove server handlers to leave only one. // NOTE: There is a limitation for Mock_session::expect_query(), not allowing // the same query to properly return a result when used consecutively (only // the first execution of the query will work and no rows are returned by the // following). Thus, getting the required server_id for more than one server // will not work using the same Mock_session. for (int i = 1; i < 3; i++) { std::string instance_address = "instance_" + std::to_string(i); cfg_global.remove_handler(instance_address); } // Set auto-increment for multi-primary with 1 server handler. mock_session ->expect_query( "show GLOBAL variables where `variable_name` in ('server_id')") .then_return({{"", {"Variable_name", "Value"}, {Type::String, Type::String}, {{"server_id", "4"}}}}); mock_session->expect_query("SET GLOBAL `auto_increment_increment` = 7") .then({""}); mock_session->expect_query("SET GLOBAL `auto_increment_offset` = 5") .then({""}); mysqlshdk::gr::update_auto_increment(&cfg_global, Topology_mode::MULTI_PRIMARY); cfg_global.apply(); // Remove server handlers to leave only one. // NOTE: There is a limitation for Mock_session::expect_query(), not allowing // the same query to properly return a result when used consecutively (only // the first execution of the query will work and no rows are returned by the // following). Thus, getting the required server_id for more than one server // will not work using the same Mock_session. for (int i = 1; i < 10; i++) { cfg_persist.remove_handler("instance_" + std::to_string(i)); } // Set auto-increment for multi-primary with 1 server handler with PERSIST. mock_session ->expect_query( "show GLOBAL variables where `variable_name` in ('server_id')") .then_return({{"", {"Variable_name", "Value"}, {Type::String, Type::String}, {{"server_id", "7"}}}}); mock_session->expect_query("SET PERSIST `auto_increment_increment` = 7") .then({""}); mock_session->expect_query("SET PERSIST `auto_increment_offset` = 1") .then({""}); mysqlshdk::gr::update_auto_increment(&cfg_persist, Topology_mode::MULTI_PRIMARY); cfg_persist.apply(); } TEST_F(Group_replication_test, get_group_protocol_version) { using mysqlshdk::db::Type; std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; EXPECT_CALL(*mock_session, get_server_version()) .WillOnce(Return(mysqlshdk::utils::Version("8.0.16"))); mock_session ->expect_query("SELECT group_replication_get_communication_protocol()") .then_return({{"", {"group_replication_get_communication_protocol()"}, {Type::String}, {{"8.0.16"}}}}); EXPECT_EQ(mysqlshdk::gr::get_group_protocol_version(instance), mysqlshdk::utils::Version("8.0.16")); } TEST_F(Group_replication_test, is_protocol_upgrade_possible) { using mysqlshdk::db::Type; mysqlshdk::utils::Version out_protocol_version; std::string server_uuid = "2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f"; std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; // Protocol version 8.0.16, one of the members is running 8.0.15 so the // upgrade is not possible mock_session ->expect_query( "SELECT m.member_id, m.member_state, m.member_host, m.member_port, " "m.member_role, m.member_version, s.view_id FROM " "performance_schema.replication_group_members m LEFT JOIN " "performance_schema.replication_group_member_stats s ON " "m.member_id = s.member_id AND s.channel_name = " "'group_replication_applier' WHERE m.member_id = @@server_uuid OR " "m.member_state <> 'ERROR' ORDER BY m.member_id") .then_return( {{"", {"member_id", "member_state", "member_host", "member_port", "member_role", "member_version", "view_id", "single_primary"}, {Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::UInteger}, {{"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544d", "ONLINE", "T480", "3310", "PRIMARY", "8.0.16", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544e", "ONLINE", "T480", "3320", "SECONDARY", "8.0.16", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f", "ONLINE", "T480", "3330", "SECONDARY", "8.0.15", "11111-", "1"}}}}); EXPECT_CALL(*mock_session, get_server_version()) .WillOnce(Return(mysqlshdk::utils::Version("8.0.16"))); mock_session ->expect_query("SELECT group_replication_get_communication_protocol()") .then_return({{"", {"group_replication_get_communication_protocol()"}, {Type::String}, {{"5.7.14"}}}}); EXPECT_FALSE(mysqlshdk::gr::is_protocol_upgrade_possible( instance, "", &out_protocol_version)); // Protocol version 8.0.16, removing the instance with 8.0.15 from the group mock_session ->expect_query( "SELECT m.member_id, m.member_state, m.member_host, m.member_port, " "m.member_role, m.member_version, s.view_id FROM " "performance_schema.replication_group_members m LEFT JOIN " "performance_schema.replication_group_member_stats s ON " "m.member_id = s.member_id AND s.channel_name = " "'group_replication_applier' WHERE m.member_id = @@server_uuid OR " "m.member_state <> 'ERROR' ORDER BY m.member_id") .then_return( {{"", {"member_id", "member_state", "member_host", "member_port", "member_role", "member_version", "view_id", "single_primary"}, {Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::UInteger}, {{"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544d", "ONLINE", "T480", "3310", "PRIMARY", "8.0.16", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544e", "ONLINE", "T480", "3320", "SECONDARY", "8.0.16", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f", "ONLINE", "T480", "3330", "SECONDARY", "8.0.15", "11111-", "1"}}}}); mock_session ->expect_query("SELECT group_replication_get_communication_protocol()") .then_return({{"", {"group_replication_get_communication_protocol()"}, {Type::String}, {{"5.7.14"}}}}); mock_session ->expect_query( "show GLOBAL variables where `variable_name` in ('server_uuid')") .then_return( {{"", {"Variable_name", "Value"}, {Type::String, Type::String}, {{"server_uuid", "2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f"}}}}); EXPECT_TRUE(mysqlshdk::gr::is_protocol_upgrade_possible( instance, server_uuid, &out_protocol_version)); EXPECT_EQ(out_protocol_version, mysqlshdk::utils::Version("8.0.16")); // Protocol version 8.0.27 mock_session = std::make_shared<Mock_session>(); instance = mysqlsh::dba::Instance(mock_session); mock_session ->expect_query( "SELECT m.member_id, m.member_state, m.member_host, m.member_port, " "m.member_role, m.member_version, s.view_id FROM " "performance_schema.replication_group_members m LEFT JOIN " "performance_schema.replication_group_member_stats s ON " "m.member_id = s.member_id AND s.channel_name = " "'group_replication_applier' WHERE m.member_id = @@server_uuid OR " "m.member_state <> 'ERROR' ORDER BY m.member_id") .then_return( {{"", {"member_id", "member_state", "member_host", "member_port", "member_role", "member_version", "view_id", "single_primary"}, {Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::UInteger}, {{"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544d", "ONLINE", "T480", "3310", "PRIMARY", "8.0.27", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544e", "ONLINE", "T480", "3320", "SECONDARY", "8.0.28", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f", "ONLINE", "T480", "3330", "SECONDARY", "8.0.29", "11111-", "1"}}}}); EXPECT_CALL(*mock_session, get_server_version()) .WillOnce(Return(mysqlshdk::utils::Version("8.0.16"))); mock_session ->expect_query("SELECT group_replication_get_communication_protocol()") .then_return({{"", {"group_replication_get_communication_protocol()"}, {Type::String}, {{"8.0.16"}}}}); mock_session ->expect_query( "show GLOBAL variables where `variable_name` in ('server_uuid')") .then_return( {{"", {"Variable_name", "Value"}, {Type::String, Type::String}, {{"server_uuid", "2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f"}}}}); out_protocol_version = mysqlshdk::utils::Version(0, 0, 0); EXPECT_TRUE(mysqlshdk::gr::is_protocol_upgrade_possible( instance, "", &out_protocol_version)); EXPECT_EQ(out_protocol_version, mysqlshdk::utils::Version("8.0.27")); } TEST_F(Group_replication_test, is_protocol_upgrade_not_required) { using mysqlshdk::db::Type; mysqlshdk::utils::Version out_protocol_version; std::string server_uuid = "2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f"; std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; mock_session ->expect_query( "SELECT m.member_id, m.member_state, m.member_host, m.member_port, " "m.member_role, m.member_version, s.view_id FROM " "performance_schema.replication_group_members m LEFT JOIN " "performance_schema.replication_group_member_stats s ON " "m.member_id = s.member_id AND s.channel_name = " "'group_replication_applier' WHERE m.member_id = @@server_uuid OR " "m.member_state <> 'ERROR' ORDER BY m.member_id") .then_return( {{"", {"member_id", "member_state", "member_host", "member_port", "member_role", "member_version", "view_id", "single_primary"}, {Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::String, Type::UInteger}, {{"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544d", "ONLINE", "T480", "3310", "PRIMARY", "8.0.15", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544e", "ONLINE", "T480", "3320", "SECONDARY", "8.0.15", "11111-", "1"}, {"2aebeab3-39d1-11e9-b4e9-9ed7ce0b544f", "ONLINE", "T480", "3330", "SECONDARY", "8.0.16", "11111-", "1"}}}}); EXPECT_CALL(*mock_session, get_server_version()) .WillOnce(Return(mysqlshdk::utils::Version("8.0.16"))); mock_session ->expect_query("SELECT group_replication_get_communication_protocol()") .then_return({{"", {"group_replication_get_communication_protocol()"}, {Type::String}, {{"5.7.14"}}}}); mock_session ->expect_query( "show GLOBAL variables where `variable_name` in ('server_uuid')") .then_return( {{"", {"Variable_name", "Value"}, {Type::String, Type::String}, {{"server_uuid", "2aebeab3-39d1-11e9-b4e9-9ed7ce0b544e"}}}}); EXPECT_FALSE(mysqlshdk::gr::is_protocol_upgrade_possible( instance, server_uuid, &out_protocol_version)); EXPECT_EQ(out_protocol_version, mysqlshdk::utils::Version()); } TEST_F(Group_replication_test, check_instance_version_compatibility) { using mysqlshdk::db::Type; using mysqlshdk::utils::Version; auto test = [](const std::optional<bool> &gr_allow_lower_version_join, const Version &instance_version, const Version &lowest_cluster_version, bool is_compatible) { std::string compatible = (is_compatible) ? "COMPATIBLE" : "NOT COMPATIBLE"; std::string str_gr_allow_lower_version_join; std::vector<std::vector<std::string>> rows_gr_allow_lower_version_join; if (!gr_allow_lower_version_join.has_value()) { str_gr_allow_lower_version_join = "NULL"; rows_gr_allow_lower_version_join = {}; } else { if (*gr_allow_lower_version_join) { str_gr_allow_lower_version_join = "ON"; rows_gr_allow_lower_version_join = { {"group_replication_allow_local_lower_version_join", "ON"}}; } else { str_gr_allow_lower_version_join = "ON"; rows_gr_allow_lower_version_join = { {"group_replication_allow_local_lower_version_join", "OFF"}}; } } SCOPED_TRACE("Test version " + compatible + ": instance version '" + instance_version.get_base() + "', lower cluster version '" + lowest_cluster_version.get_base() + "', gr_allow_lower_version_join '" + str_gr_allow_lower_version_join + "'"); std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; mock_session ->expect_query( "show GLOBAL variables where `variable_name` in " "('group_replication_allow_local_lower_version_join')") .then_return({{"", {"Variable_name", "Value"}, {Type::String, Type::String}, rows_gr_allow_lower_version_join}}); if (!gr_allow_lower_version_join.has_value() || !*gr_allow_lower_version_join) { EXPECT_CALL(*mock_session, get_server_version()) .WillOnce(Return(instance_version)); } if (is_compatible) { EXPECT_NO_THROW(mysqlshdk::gr::check_instance_version_compatibility( instance, lowest_cluster_version)); } else { std::string major, str_instance_version, str_cluster_version; if (instance_version <= mysqlshdk::utils::Version(8, 0, 16)) { major = "major "; str_instance_version = std::to_string(instance_version.get_major()); str_cluster_version = std::to_string(lowest_cluster_version.get_major()); } else { major = ""; str_instance_version = instance_version.get_base(); str_cluster_version = lowest_cluster_version.get_base(); } EXPECT_THROW_LIKE(mysqlshdk::gr::check_instance_version_compatibility( instance, lowest_cluster_version), std::runtime_error, "Instance " + major + "version '" + str_instance_version + "' cannot be lower than the " "cluster lowest " + major + "version '" + str_cluster_version + "'."); } }; #define ALLOWED(gr_allow_lower_version_join, instance_version, \ lowest_cluster_version) \ do { \ SCOPED_TRACE("ALLOWED"); \ test(gr_allow_lower_version_join, instance_version, \ lowest_cluster_version, true); \ } while (0); #define BLOCKED(gr_allow_lower_version_join, instance_version, \ lowest_cluster_version) \ do { \ SCOPED_TRACE("BLOCKED"); \ test(gr_allow_lower_version_join, instance_version, \ lowest_cluster_version, false); \ } while (0); // major upgrades ALLOWED(std::optional<bool>(false), Version(8, 1, 0), Version(8, 0, 40)); ALLOWED(std::optional<bool>(false), Version(8, 2, 0), Version(8, 1, 0)); ALLOWED(std::optional<bool>(false), Version(8, 4, 0), Version(8, 1, 0)); // upgrades that are not not supported but we don't check for ALLOWED(std::optional<bool>(false), Version(8, 3, 0), Version(8, 1, 0)); ALLOWED(std::optional<bool>(false), Version(12, 0, 0), Version(8, 1, 0)); // basic downgrade ALLOWED(std::optional<bool>(false), Version(8, 1, 0), Version(8, 1, 2)); ALLOWED(std::optional<bool>(false), Version(8, 4, 0), Version(8, 4, 1)); BLOCKED(std::optional<bool>(false), Version(8, 1, 0), Version(8, 2, 0)); BLOCKED(std::optional<bool>(false), Version(8, 3, 0), Version(8, 4, 0)); // Test: group_replication_allow_local_lower_version_join = ON // - Version compatible, independently of the cluster lowest // version. ALLOWED(std::optional<bool>(true), Version(8, 0, 1), Version(8, 0, 20)); ALLOWED(std::optional<bool>(true), Version(5, 7, 0), Version(5, 7, 50)); ALLOWED(std::optional<bool>(true), Version(8, 0, 21), Version(8, 0, 20)); ALLOWED(std::optional<bool>(true), Version(5, 7, 51), Version(5, 7, 50)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version <= 8.0.16 // Version compatible, if MAJOR(version) >= // MAJOR(lowest_cluster_version) ALLOWED(std::optional<bool>(false), Version(8, 0, 16), Version(8, 0, 16)); ALLOWED(std::optional<bool>(false), Version(8, 0, 15), Version(8, 0, 16)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version <= 8.0.16 // Version incompatible, if MAJOR(version) < // MAJOR(lowest_cluster_version) BLOCKED(std::optional<bool>(false), Version(5, 7, 26), Version(8, 0, 16)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version > 8.0.16 // Version compatible, if version >= lowest_cluster_version ALLOWED(std::optional<bool>(false), Version(8, 0, 17), Version(8, 0, 17)); ALLOWED(std::optional<bool>(false), Version(8, 0, 18), Version(8, 0, 17)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version > 8.0.16 // Version incompatible, if version < lowest_cluster_version BLOCKED(std::optional<bool>(false), Version(8, 0, 17), Version(8, 0, 18)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() ALLOWED(std::optional<bool>(false), Version(8, 0, 35), Version(8, 0, 35)); ALLOWED(std::optional<bool>(false), Version(8, 4, 35), Version(8, 4, 35)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version non-LTS // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() ALLOWED(std::optional<bool>(false), Version(8, 2, 0), Version(8, 1, 0)); ALLOWED(std::optional<bool>(false), Version(8, 1, 0), Version(8, 0, 35)); ALLOWED(std::optional<bool>(false), Version(8, 1, 1), Version(8, 1, 0)); ALLOWED(std::optional<bool>(false), Version(8, 1, 0), Version(8, 1, 1)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version && // && lowest_cluster_version.series() == version.series() ALLOWED(std::optional<bool>(false), Version(8, 0, 36), Version(8, 0, 35)); ALLOWED(std::optional<bool>(false), Version(8, 4, 36), Version(8, 4, 35)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() ALLOWED(std::optional<bool>(false), Version(8, 0, 38), Version(8, 0, 37)); ALLOWED(std::optional<bool>(false), Version(8, 4, 38), Version(8, 4, 37)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() BLOCKED(std::optional<bool>(false), Version(8, 0, 34), Version(8, 0, 35)); BLOCKED(std::optional<bool>(false), Version(8, 3, 1), Version(8, 4, 0)); ALLOWED(std::optional<bool>(false), Version(8, 0, 35), Version(8, 0, 36)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version non-LTS // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() BLOCKED(std::optional<bool>(false), Version(8, 1, 0), Version(8, 2, 18)); BLOCKED(std::optional<bool>(false), Version(8, 0, 35), Version(8, 1, 0)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() BLOCKED(std::optional<bool>(false), Version(8, 0, 33), Version(8, 0, 34)); ALLOWED(std::optional<bool>(false), Version(8, 3, 2), Version(8, 3, 34)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() BLOCKED(std::optional<bool>(false), Version(8, 0, 35), Version(8, 1, 0)); BLOCKED(std::optional<bool>(false), Version(8, 4, 2), Version(9, 0, 0)); // Test: group_replication_allow_local_lower_version_join = OFF // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() BLOCKED(std::optional<bool>(false), Version(8, 0, 36), Version(8, 2, 0)); BLOCKED(std::optional<bool>(false), Version(8, 4, 36), Version(9, 2, 0)); // // Test: group_replication_allow_local_lower_version_join is not // defined // instance_version <= 8.0.16 // Version compatible, if MAJOR(version) >= // MAJOR(lowest_cluster_version) ALLOWED(std::optional<bool>(), Version(8, 0, 16), Version(8, 0, 16)); ALLOWED(std::optional<bool>(), Version(8, 0, 15), Version(8, 0, 16)); // Test: group_replication_allow_local_lower_version_join is not // defined // instance_version <= 8.0.16 // Version incompatible, if MAJOR(version) < // MAJOR(lowest_cluster_version) BLOCKED(std::optional<bool>(), Version(5, 7, 26), Version(8, 0, 16)); // Test: group_replication_allow_local_lower_version_join is not // defined // instance_version > 8.0.16 // Version compatible, if version >= lowest_cluster_version ALLOWED(std::optional<bool>(), Version(8, 0, 17), Version(8, 0, 17)); ALLOWED(std::optional<bool>(), Version(8, 0, 18), Version(8, 0, 17)); // Test: group_replication_allow_local_lower_version_join is not // defined. // instance_version > 8.0.16 // Version incompatible, if version < lowest_cluster_version BLOCKED(std::optional<bool>(), Version(8, 0, 17), Version(8, 0, 18)); // Test: group_replication_allow_local_lower_version_join is not // defined. // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() ALLOWED(std::optional<bool>(), Version(8, 0, 35), Version(8, 0, 36)); ALLOWED(std::optional<bool>(), Version(8, 4, 35), Version(8, 4, 36)); // Test: group_replication_allow_local_lower_version_join is not // defined. // instance_version >= 8.0.35 // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() BLOCKED(std::optional<bool>(), Version(8, 0, 36), Version(8, 2, 0)); BLOCKED(std::optional<bool>(), Version(8, 4, 36), Version(9, 2, 0)); // Test: group_replication_allow_local_lower_version_join is not // defined. // instance_version non-LTS // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() BLOCKED(std::optional<bool>(), Version(8, 1, 0), Version(8, 2, 18)); BLOCKED(std::optional<bool>(), Version(8, 0, 35), Version(8, 1, 0)); ALLOWED(std::optional<bool>(), Version(8, 1, 0), Version(8, 1, 1)); // Test: group_replication_allow_local_lower_version_join is not // defined. // instance_version non-LTS // Version compatible, if version < lowest_cluster_version // && lowest_cluster_version.series() == version.series() ALLOWED(std::optional<bool>(), Version(8, 2, 0), Version(8, 1, 0)); ALLOWED(std::optional<bool>(), Version(8, 1, 0), Version(8, 0, 35)); ALLOWED(std::optional<bool>(), Version(8, 1, 1), Version(8, 1, 0)); #undef ALLOWED #undef BLOCKED } TEST_F(Group_replication_test, is_instance_only_read_compatible) { using mysqlshdk::db::Type; using mysqlshdk::utils::Version; auto test = [](const Version &instance_version, const Version &lowest_cluster_version, bool is_only_read_compatible) { std::string read_compatible = (is_only_read_compatible) ? "ONLY R/O COMPATIBLE" : "NOT ONLY R/O COMPATIBLE"; SCOPED_TRACE("Test " + read_compatible + ": instance version '" + instance_version.get_base() + "', lower cluster version '" + lowest_cluster_version.get_base() + "'."); std::shared_ptr<Mock_session> mock_session = std::make_shared<Mock_session>(); mysqlshdk::mysql::Instance instance{mock_session}; EXPECT_CALL(*mock_session, get_server_version()) .WillOnce(Return(instance_version)); if (is_only_read_compatible) { EXPECT_TRUE(mysqlshdk::gr::is_instance_only_read_compatible( instance, lowest_cluster_version)); } else { EXPECT_FALSE(mysqlshdk::gr::is_instance_only_read_compatible( instance, lowest_cluster_version)); } }; // Test: instance_version >= 8.0.17 and lowest_cluster_version >= 8.0.0 // Only read compatible, if version > lowest_cluster_version test(Version(8, 0, 17), Version(8, 0, 0), true); test(Version(8, 0, 18), Version(8, 0, 0), true); test(Version(8, 0, 17), Version(8, 0, 15), true); test(Version(8, 0, 17), Version(8, 0, 16), true); // BUG#30896344: CHECK FOR READ-ONLY COMPATIBILITY IN ADDINSTANCE USES WRONG // MINIMUM VERSION test(Version(8, 0, 16), Version(8, 0, 15), false); // Test: instance_version >= 8.0.17 and lowest_cluster_version >= 8.0.0 // Not only read compatible, if version <= lowest_cluster_version test(Version(8, 0, 17), Version(8, 0, 17), false); test(Version(8, 0, 17), Version(8, 0, 18), false); test(Version(8, 0, 18), Version(8, 0, 18), false); test(Version(8, 0, 18), Version(8, 0, 19), false); // Test: instance_version < 8.0.17 // Not only read compatible, independently of the lowest_cluster_version test(Version(8, 0, 16), Version(8, 0, 0), false); test(Version(8, 0, 16), Version(8, 0, 16), false); test(Version(8, 0, 16), Version(8, 0, 17), false); test(Version(8, 0, 16), Version(5, 7, 26), false); test(Version(8, 0, 0), Version(8, 0, 0), false); test(Version(8, 0, 0), Version(8, 0, 15), false); test(Version(8, 0, 0), Version(8, 0, 16), false); test(Version(8, 0, 0), Version(5, 7, 26), false); test(Version(5, 7, 26), Version(8, 0, 0), false); test(Version(5, 7, 26), Version(8, 0, 15), false); test(Version(5, 7, 26), Version(8, 0, 16), false); test(Version(5, 7, 26), Version(5, 7, 26), false); test(Version(5, 7, 26), Version(5, 7, 25), false); test(Version(5, 7, 26), Version(5, 7, 27), false); // Test: lowest_cluster_version < 8.0.0 // Not only read compatible, independently of the instance_version test(Version(8, 0, 16), Version(5, 99, 99), false); test(Version(8, 0, 0), Version(5, 99, 99), false); test(Version(8, 0, 17), Version(5, 99, 99), false); test(Version(5, 99, 99), Version(5, 99, 99), false); test(Version(5, 7, 26), Version(5, 99, 99), false); test(Version(8, 0, 16), Version(5, 7, 26), false); test(Version(8, 0, 0), Version(5, 7, 26), false); test(Version(8, 0, 17), Version(5, 7, 26), false); test(Version(5, 7, 27), Version(5, 7, 26), false); test(Version(5, 7, 26), Version(5, 7, 26), false); test(Version(5, 7, 25), Version(5, 7, 26), false); } TEST_F(Group_replication_test, is_address_supported_by_gr) { using mysqlshdk::db::Type; using mysqlshdk::utils::Version; auto test = [](const std::string &address, const Version &instance_version, bool is_supported) { std::string supported = (is_supported) ? "SUPPORTED" : "NOT SUPPORTED"; SCOPED_TRACE("Test gr_address " + supported + " with address '" + address + "' and instance version '" + instance_version.get_base() + "'."); if (is_supported) { EXPECT_TRUE(mysqlshdk::gr::is_endpoint_supported_by_gr(address, instance_version)); } else { EXPECT_FALSE(mysqlshdk::gr::is_endpoint_supported_by_gr( address, instance_version)); } }; // ipv6 must have square brackets around them EXPECT_THROW( mysqlshdk::gr::is_endpoint_supported_by_gr("::1:3301", Version(5, 0, 18)), shcore::Exception); // Must provide a port number EXPECT_THROW( mysqlshdk::gr::is_endpoint_supported_by_gr("[::1]", Version(5, 0, 18)), shcore::Exception); // IPv6 are only supported from 8.0.14 onwards test("[::1]:3301", Version(8, 0, 14), true); test("[::1]:3301", Version(8, 0, 13), false); test("[::1]:3301", Version(5, 0, 18), false); test("[fe80::929b:542:427e:4db4]:3306", Version(8, 0, 14), true); test("[fe80::929b:542:427e:4db4]:3306", Version(8, 0, 13), false); test("[fe80::929b:542:427e:4db4]:3306", Version(5, 0, 18), false); test("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:3306", Version(8, 0, 14), true); test("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:3306", Version(8, 0, 13), false); test("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:3306", Version(5, 0, 18), false); // IPv4 are always supported test("192.168.4.4:3301", Version(8, 0, 14), true); test("192.168.4.4:3301", Version(8, 0, 13), true); test("192.168.4.4:3301", Version(8, 0, 13), true); // hostnames are not resolved and assumed to be true test("sample_hostname:3302", Version(8, 0, 14), true); test("localhost:8888", Version(8, 0, 13), true); test("this_is_home:3393", Version(8, 0, 13), true); } TEST_F(Group_replication_test, ip_whitelist) { using V = const mysqlshdk::utils::Version; { const std::string ip_whitelist{}; EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.17"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist: string value cannot be empty."); // Verify that the option name is presented as ipAllowlist when used EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.22"}, ip_whitelist, mysqlsh::dba::kIpAllowlist), shcore::Exception, "Invalid value for ipAllowlist: string value cannot be empty."); } { const std::string ip_whitelist{ "192.168.0.0/24,::ffff:10.2.0.3/128,::10.3.0.3/128,::ffff:10.2.0.3/16," "::10.3.0.3/16,10.100.23.1/128"}; EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.14"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist '10.100.23.1/128': subnet value in CIDR " "notation is not supported for IPv4 addresses."); } { const std::string ip_whitelist{ "192.168.0.1/8,127.0.0.1, 10.123.4.11, ::1,mysql.cluster.example.com"}; mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.17"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.14"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.13"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist '::1': IPv6 not supported"); EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.10"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist '::1': IPv6 not supported"); EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.4"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist '::1': IPv6 not supported"); EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.3"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist '::1': IPv6 not supported"); EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"5.7.27"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist '::1': IPv6 not supported"); } { const std::string ip_whitelist{ "192.168.0.1/8,127.0.0.1,10.123.4.11, mysql.cluster.example.com"}; mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.17"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.14"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.13"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.10"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.4"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.3"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist 'mysql.cluster.example.com': hostnames " "are not supported"); EXPECT_THROW_LIKE( mysqlsh::dba::validate_ip_whitelist_option(V{"5.7.27"}, ip_whitelist, mysqlsh::dba::kIpWhitelist), shcore::Exception, "Invalid value for ipWhitelist 'mysql.cluster.example.com': hostnames " "are not supported"); } { const std::string ip_whitelist{"192.168.0.1/8, 127.0.0.1,10.123.4.11"}; mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.17"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.14"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.13"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.10"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.4"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"8.0.3"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); mysqlsh::dba::validate_ip_whitelist_option(V{"5.7.27"}, ip_whitelist, mysqlsh::dba::kIpWhitelist); } } } // namespace testing