modules/adminapi/common/instance_validations.cc (568 lines of code) (raw):
/*
* Copyright (c) 2018, 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 "modules/adminapi/common/instance_validations.h"
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "modules/adminapi/common/dba_errors.h"
#include "modules/adminapi/common/metadata_storage.h"
#include "modules/adminapi/common/provision.h"
#include "modules/adminapi/common/sql.h"
#include "modules/adminapi/mod_dba.h"
#include "mysqlshdk/include/shellcore/console.h"
#include "mysqlshdk/libs/config/config_server_handler.h"
#include "mysqlshdk/libs/mysql/replication.h"
#include "mysqlshdk/libs/textui/textui.h"
#include "mysqlshdk/libs/utils/utils_net.h"
#include "mysqlshdk/libs/utils/version.h"
// Naming convention for validations:
//
// - check_ for generic check functions, which should not print anything and
// instead should return the result of its check so that the caller can take
// action on it.
//
// - validate_ for validation functions that print messages for issues, but
// not contextual messages. They should return the general result of the
// validation (e.g. pass/fail) for the caller to decide what to do.
//
// - ensure_ performs a validation, prints out errors, contextual info
// and throws an exception on validation error. They should usually be wrappers
// around a validate_ or check_ functions.
//
// checks and validations are mostly generic code, ensure is AdminAPI specific
// and thus should be in mod_dba_common.cc (or whatever it's split into)
namespace mysqlsh {
namespace dba {
namespace checks {
/**
* Perform validation of schemas for compatibility issues with group
* replication. If any issues are found, they're printed to the console.
*
* Checks performed are:
* - GR compatible storage engines only (InnoDB and MEMORY)
* - all tables must have a PK or a UNIQUE NOT NULL key
*
* @param instance target instance for the validation. Must be authenticated
* with an account with SELECT access to all schemas.
* @param skip_check_tables_pk true to skip checking tables without PKs
* @return true if no issues found.
*/
bool validate_schemas(const mysqlshdk::mysql::IInstance &instance,
bool skip_check_tables_pk) {
bool ok = true;
std::string k_gr_compliance_skip_schemas =
"('mysql', 'sys', 'performance_schema', 'information_schema')";
std::string k_gr_compliance_skip_engines = "('InnoDB', 'MEMORY')";
auto console = mysqlsh::current_console();
{
std::shared_ptr<mysqlshdk::db::IResult> result = instance.query(
"SELECT table_schema, table_name, engine "
" FROM information_schema.tables "
" WHERE engine NOT IN " +
k_gr_compliance_skip_engines + " AND table_schema NOT IN " +
k_gr_compliance_skip_schemas);
auto row = result->fetch_one();
if (row) {
console->print_error(
"The following tables use a storage engine that are not supported by "
"Group Replication:");
ok = false;
std::string tables;
while (row) {
if (!tables.empty()) tables.append(", ");
tables.append(row->get_string(0))
.append(".")
.append(row->get_string(1));
row = result->fetch_one();
}
tables.append("\n");
console->print_info(tables);
}
}
if (!skip_check_tables_pk) {
std::shared_ptr<mysqlshdk::db::IResult> result = instance.query(
"SELECT t.table_schema, t.table_name "
"FROM information_schema.tables t "
" LEFT JOIN (SELECT table_schema, table_name "
" FROM information_schema.statistics "
" GROUP BY table_schema, table_name, index_name "
" HAVING SUM(CASE "
" WHEN non_unique = 0 AND nullable != 'YES' "
" THEN 1 ELSE 0 END) = COUNT(*) "
" ) puks "
" ON t.table_schema = puks.table_schema "
" AND t.table_name = puks.table_name "
"WHERE puks.table_name IS NULL "
" AND t.table_type = 'BASE TABLE' "
" AND t.table_schema NOT IN " +
k_gr_compliance_skip_schemas);
auto row = result->fetch_one();
if (row) {
console->print_error(
"The following tables do not have a Primary Key or equivalent "
"column: ");
ok = false;
std::string tables;
while (row) {
if (!tables.empty()) tables.append(", ");
tables.append(row->get_string(0))
.append(".")
.append(row->get_string(1));
row = result->fetch_one();
}
tables.append("\n");
console->print_info(tables);
}
}
if (!ok) {
console->print_info(
"Group Replication requires tables to use InnoDB and "
"have a PRIMARY KEY or PRIMARY KEY Equivalent (non-null "
"unique key). Tables that do not follow these "
"requirements will be readable but not updateable "
"when used with Group Replication. "
"If your applications make updates (INSERT, UPDATE or "
"DELETE) to these tables, ensure they use the InnoDB "
"storage engine and have a PRIMARY KEY or PRIMARY KEY "
"Equivalent.");
console->print_info(
"If you can't change the tables structure to include an extra visible "
"key to be used as PRIMARY KEY, you can make use of the INVISIBLE "
"COLUMN feature available since 8.0.23: "
"https://dev.mysql.com/doc/refman/8.0/en/invisible-columns.html");
}
return ok;
}
/**
* Validate if a supported InnoDB page size value is used.
*
* Currently, innodb_page_size > 4k is required due to the size of some
* required information in the Metadata.
*
* @param instance target instance to check.
*/
void validate_innodb_page_size(mysqlshdk::mysql::IInstance *instance) {
log_info("Validating InnoDB page size of instance '%s'.",
instance->descr().c_str());
std::string page_size = *instance->get_sysvar_string(
"innodb_page_size", mysqlshdk::mysql::Var_qualifier::GLOBAL);
auto console = mysqlsh::current_console();
int page_size_value = std::stoul(page_size);
if (page_size_value <= 4096) {
console->print_error(
"Instance '" + instance->descr() +
"' is using a non-supported InnoDB "
"page size (innodb_page_size=" +
page_size +
"). Only instances with "
"innodb_page_size greater than 4k (4096) can be used with InnoDB "
"Cluster.");
throw shcore::Exception::runtime_error(
"Unsupported innodb_page_size value: " + page_size);
}
}
/**
* Validate that the target instances host is correctly configured in MySQL.
* The check will be based on the @@hostname and @@report_host sysvars,
* where @@report_host takes precedence if it's not NULL.
*
* The host address will be printed to the console along with an informational
* message.
*
* If the target host name resolves to a loopback address (127.*) an error
* will be printed and the function returns false. Unless the target is a
* sandbox, detected by checking that the name of the datadir is sandboxdata.
*
* If an IPv6 address is used and the instance version is < 8.0.14
* throw an exception.
*
* @param instance target instance with cached global sysvars
* @param verbose if 1 additional informational messages are shown, if 2
* further explanations are provided
* @throw an exception if the host name configuration is not suitable
*/
void validate_host_address(const mysqlshdk::mysql::IInstance &instance,
int verbose) {
auto console = mysqlsh::current_console();
bool report_host_set;
std::string hostname;
std::string report_host;
auto result = instance.query("SELECT @@hostname, @@report_host");
auto row = result->fetch_one_or_throw();
hostname = row->get_string(0);
report_host = row->get_string(1, "");
report_host_set = !row->is_null(1);
if (report_host_set) {
log_debug("Target has report_host=%s", report_host.c_str());
log_debug("Target has hostname=%s", hostname.c_str());
hostname = report_host;
} else {
log_debug("Target has report_host=NULL");
log_debug("Target has hostname=%s", hostname.c_str());
}
if (report_host_set && report_host.empty()) {
console->print_error("Invalid 'report_host' value for instance '" +
instance.get_connection_options().uri_endpoint() +
"'. The value cannot be empty if defined.");
// NOTE: The value for report_host can be set to an empty string which is
// invalid. If defined the report_host value should not be an empty
// string, otherwise it is used by replication as an empty string "".
throw std::runtime_error(
"The value for variable 'report_host' cannot be empty.");
}
if (verbose > 0) {
console->print_info();
console->print_info(
"This instance reports its own address as " +
mysqlshdk::textui::bold(instance.get_canonical_address()));
if (!report_host_set && verbose > 1) {
console->print_info(
"Clients and other cluster members will communicate with it through "
"this address by default. "
"If this is not correct, the report_host MySQL "
"system variable should be changed.");
}
}
// Validate the IP of the hostname used for GR.
try {
std::string seed_ip =
mysqlshdk::utils::Net::resolve_hostname_ipv4(hostname);
const bool is_loopback = mysqlshdk::utils::Net::is_loopback(hostname);
if (is_loopback && seed_ip != "127.0.0.1") {
// Loopback IPv4 addresses except for 127.0.0.1 are not supported by GCS
// leading to errors.
console->print_error(shcore::str_format(
"Cannot use host '%s' for instance '%s' because it resolves to an IP "
"address (%s) that does not "
"match a real network interface, thus it is not supported. Change "
"your system settings and/or "
"set the MySQL server 'report_host' variable to a hostname that "
"resolves to a supported IP address.",
hostname.c_str(), instance.descr().c_str(), seed_ip.c_str()));
throw std::runtime_error(shcore::str_format(
"Invalid host/IP '%s' resolves to '%s' which is not "
"supported.",
hostname.c_str(), seed_ip.c_str()));
}
} catch (const mysqlshdk::utils::net_error &error) {
// if it is an IPv6 address and the instance version does not support IPv6
// throw an error
// Do not try to resolve hostnames. Even if we can resolve them, there is
// no guarantee that name resolution is the same across cluster instances
// and the instance where we are running the ngshell
bool is_ipv6 = mysqlshdk::utils::Net::is_ipv6(hostname);
bool supports_ipv6 =
instance.get_version() >= mysqlshdk::utils::Version(8, 0, 14);
if (is_ipv6 && !supports_ipv6) {
// Using an IPv6 address and the instance does not support it.
console->print_error(shcore::str_format(
"Cannot use host '%s' for instance '%s' "
"because it is an IPv6 address which is only supported by "
"Group Replication from MySQL version >= 8.0.14. Set the MySQL "
"server 'report_host' "
"variable to an IPv4 address or hostname that resolves an IPv4 "
"address.",
hostname.c_str(), instance.descr().c_str()));
throw std::runtime_error(shcore::str_format(
"Unsupported IP address '%s'. IPv6 is only "
"supported by Group Replication on MySQL version >= 8.0.14.",
hostname.c_str()));
}
}
}
void process_row_values(const mysqlshdk::mysql::Invalid_config &cfg,
shcore::Value::Map_type_ref row) {
// adjust the displayed string for "log_bin"
if (cfg.var_name == "log_bin") {
if (cfg.current_val == mysqlshdk::mysql::k_value_not_set)
(*row)["current"] = shcore::Value("<not present>");
else
(*row)["current"] = shcore::Value(cfg.current_val);
if (cfg.required_val == mysqlshdk::mysql::k_value_not_set ||
cfg.required_val == mysqlshdk::mysql::k_no_value)
(*row)["required"] = shcore::Value("ON");
else
(*row)["required"] = shcore::Value(cfg.required_val);
return;
}
if (cfg.var_name == "skip_log_bin" || cfg.var_name == "disable_log_bin") {
if (cfg.current_val == mysqlshdk::mysql::k_value_not_set)
(*row)["current"] = shcore::Value("<present>");
else
(*row)["current"] = shcore::Value(cfg.current_val);
if (cfg.required_val == mysqlshdk::mysql::k_value_not_set)
(*row)["required"] = shcore::Value("<not present>");
else
(*row)["required"] = shcore::Value(cfg.required_val);
return;
}
// store as is
(*row)["current"] = shcore::Value(cfg.current_val);
(*row)["required"] = shcore::Value(cfg.required_val);
}
void process_row_action(const mysqlshdk::mysql::Invalid_config &cfg,
shcore::Value::Map_type_ref row,
std::string_view action) {
if (cfg.var_name == "skip_log_bin" || cfg.var_name == "disable_log_bin") {
(*row)["action"] = shcore::Value("remove_opt+restart");
return;
}
// store as is
(*row)["action"] = shcore::Value(action);
}
/**
* Validate configuration of the target MySQL server instance.
*
* Configuration settings are checked through sysvars, but if mycnf_path is
* given the config file will also be checked. If any changes are required,
* they will be sent to the console. Also returns flags indicating the types
* of changes that are required, if any.
*
* @param instance target instance
* @param mycnf_path optional config file path, for local instances
* @param config pointer to config handler
* @param cluster_type target cluster/replicaset type
* @param can_persist true if instance has support to persist
* variables, false if has but it is disabled and
* null if it is not supported.
* @param restart_needed[out] true if instance needs to be restarted
* @param mycnf_change_needed[out] true if my.cnf has to be updated
* @param sysvar_change_needed[out] true if sysvars have to be updated
* @param ret_val[out] assigned to check results
* @return [description]
*/
std::vector<mysqlshdk::mysql::Invalid_config> validate_configuration(
mysqlshdk::mysql::IInstance *instance, const std::string &mycnf_path,
mysqlshdk::config::Config *const config, Cluster_type cluster_type,
std::optional<bool> can_persist, bool *restart_needed,
bool *mycnf_change_needed, bool *sysvar_change_needed,
shcore::Value *ret_val) {
// Check supported innodb_page_size (must be > 4k). See: BUG#27329079
validate_innodb_page_size(instance);
// Check if performance_schema is enabled
// BUG#25867733
validate_performance_schema_enabled(*instance);
log_info("Validating configuration of %s (mycnf = %s)",
instance->descr().c_str(), mycnf_path.c_str());
// Perform check with no update
std::vector<mysqlshdk::mysql::Invalid_config> invalid_cfs_vec =
check_instance_config(*instance, *config, cluster_type);
// Sort invalid cfs_vec by the name of the variable
std::sort(invalid_cfs_vec.begin(), invalid_cfs_vec.end());
// Build the map to be used by the print_validation results
shcore::Value::Map_type_ref map_val(new shcore::Value::Map_type());
shcore::Value check_result = shcore::Value(map_val);
*restart_needed = false;
*mycnf_change_needed = false;
*sysvar_change_needed = false;
bool log_bin_present = false;
if (invalid_cfs_vec.empty()) {
(*map_val)["status"] = shcore::Value("ok");
} else {
(*map_val)["status"] = shcore::Value("error");
shcore::Value::Array_type_ref config_errors(
new shcore::Value::Array_type());
for (const mysqlshdk::mysql::Invalid_config &cfg : invalid_cfs_vec) {
shcore::Value::Map_type_ref error(new shcore::Value::Map_type());
std::string_view action;
if (!log_bin_present &&
(cfg.var_name == "log_bin" || cfg.var_name == "skip_log_bin" ||
cfg.var_name == "disable_log_bin") &&
cfg.types.is_set(mysqlshdk::mysql::Config_type::CONFIG)) {
log_bin_present = true;
}
if (log_bin_present ||
(cfg.types.is_set(mysqlshdk::mysql::Config_type::CONFIG) &&
cfg.types.is_set(mysqlshdk::mysql::Config_type::SERVER))) {
*mycnf_change_needed = true;
if (cfg.restart) {
*restart_needed = true;
action = "config_update+restart";
} else {
action = "server_update+config_update";
*sysvar_change_needed = true;
}
} else if (cfg.types.is_set(mysqlshdk::mysql::Config_type::CONFIG)) {
*mycnf_change_needed = true;
action = "config_update";
} else if (cfg.types.is_set(mysqlshdk::mysql::Config_type::SERVER)) {
*sysvar_change_needed = true;
if (cfg.restart) {
*restart_needed = true;
action = "server_update+restart";
} else {
action = "server_update";
}
} else if (cfg.types.is_set(
mysqlshdk::mysql::Config_type::RESTART_ONLY)) {
*restart_needed = true;
action = "restart";
} else if (cfg.types.empty()) {
throw std::runtime_error("Unexpected change type");
}
(*error)["option"] = shcore::Value(cfg.var_name);
process_row_values(cfg, error);
process_row_action(cfg, error, action);
if (!cfg.persisted_val.is_null()) {
(*error)["persisted"] = shcore::Value(*cfg.persisted_val);
}
config_errors->push_back(shcore::Value(std::move(error)));
}
(*map_val)["config_errors"] = shcore::Value(config_errors);
}
if (ret_val) *ret_val = check_result;
log_debug("Check command returned: %s", check_result.descr().c_str());
if (!invalid_cfs_vec.empty()) {
auto console = mysqlsh::current_console();
console->print_info();
print_validation_results(check_result.as_map(), true);
if (*restart_needed) {
// if we have to change some read only variables, print a message to the
// user.
std::string base_msg =
"Some variables need to be changed, but cannot be done dynamically "
"on the server";
if (log_bin_present && *mycnf_change_needed) {
base_msg += ": an option file is required";
} else {
if (*mycnf_change_needed) {
if (!can_persist.has_value()) {
// 5.7 server, set persist is not supported
base_msg += ": an option file is required";
} else if (!*can_persist) {
// 8.0 server with set persist support disabled
base_msg +=
": set persist support is disabled. Enable it or provide an "
"option file";
}
}
}
base_msg += ".";
console->print_info(base_msg);
}
}
return invalid_cfs_vec;
}
void validate_performance_schema_enabled(
const mysqlshdk::mysql::IInstance &instance) {
log_info("Checking if performance_schema is enabled on instance '%s'.",
instance.descr().c_str());
if (instance.get_sysvar_bool("performance_schema", false)) {
return;
}
mysqlsh::current_console()->print_error(shcore::str_format(
"Instance '%s' has the performance_schema disabled "
"(performance_schema=OFF). Instances must have the performance_schema "
"enabled to for InnoDB Cluster usage.",
instance.descr().c_str()));
throw shcore::Exception::runtime_error(
"performance_schema disabled on target instance.");
}
void ensure_instance_not_belong_to_cluster(
const std::shared_ptr<Instance> &instance,
const std::shared_ptr<Instance> &cluster_instance,
const std::string &cluster_id) {
auto metadata = std::make_shared<MetadataStorage>(instance);
auto type = mysqlsh::dba::get_instance_type(*metadata, *instance);
switch (type) {
// these types don't need any more checks
case TargetType::Standalone:
case TargetType::StandaloneWithMetadata:
case TargetType::StandaloneInMetadata:
case TargetType::AsyncReplication:
return;
default:
break;
}
if (type != TargetType::InnoDBClusterSetOffline) {
// Retrieves the new instance UUID
std::string uuid = instance->get_uuid();
// Verifies if this UUID is part of the current replication group
std::vector<mysqlshdk::gr::Member> members =
mysqlshdk::gr::get_members(*cluster_instance);
if (std::find_if(members.begin(), members.end(),
[&uuid](const auto &member) {
return member.uuid == uuid;
}) != members.end()) {
if (type == TargetType::InnoDBCluster ||
type == TargetType::InnoDBClusterSet) {
log_debug("Instance '%s' already managed by InnoDB Cluster",
instance->descr().c_str());
throw shcore::Exception("The instance '" + instance->descr() +
"' is already part of this InnoDB Cluster",
SHERR_DBA_BADARG_INSTANCE_MANAGED_IN_CLUSTER);
}
current_console()->print_error(
"Instance '" + instance->descr() +
"' is part of the Group Replication group but is not in the "
"metadata. Please use <Cluster>.rescan() to update the "
"metadata.");
throw shcore::Exception(
"Metadata inconsistent: " + instance->descr() +
" is a group member but is not in the metadata.",
SHERR_DBA_ASYNC_MEMBER_INCONSISTENT);
}
} else {
auto metadata_cluster = std::make_shared<MetadataStorage>(cluster_instance);
try {
// if instance is in the clusters metadata, it's valid
metadata_cluster->get_instance_by_uuid(instance->get_uuid());
return;
} catch (const shcore::Exception &exp) {
if (exp.code() != SHERR_DBA_MEMBER_METADATA_MISSING) throw;
}
// check if the instance cluster belongs to the target's cluster set
Cluster_metadata instance_cluster_meta;
if (metadata->get_cluster_for_server_uuid(instance->get_uuid(),
&instance_cluster_meta)) {
if (instance_cluster_meta.cluster_id == cluster_id) return;
if (std::string cs_id; metadata_cluster->check_cluster_set(
nullptr, nullptr, nullptr, &cs_id)) {
std::vector<Cluster_set_member_metadata> cs_members;
if (metadata_cluster->get_cluster_set(cs_id, false, nullptr,
&cs_members)) {
auto it = std::find_if(
cs_members.begin(), cs_members.end(),
[&cs_id = instance_cluster_meta.cluster_id](const auto &member) {
return (cs_id == member.cluster.cluster_id);
});
if (it != cs_members.end()) return;
}
}
}
// reaching this point, we have a cluster set that doesn't belong to the
// target cluster or its cluster doesn't belong to the target's cluster set,
// so treat it as if it was an (unknown) cluster set
type = TargetType::InnoDBClusterSet;
}
switch (type) {
case TargetType::InnoDBCluster:
case TargetType::InnoDBClusterSet:
// Check if instance is running auto-rejoin and warn user.
if (mysqlshdk::gr::is_running_gr_auto_rejoin(*instance)) {
throw shcore::Exception::runtime_error(
"The instance '" + instance->descr() +
"' is currently attempting to rejoin the cluster. Use <cluster>."
"rejoinInstance() if you want to override the auto-rejoin "
"process.");
} else {
throw shcore::Exception::runtime_error(
"The instance '" + instance->descr() +
"' is already part of another InnoDB Cluster");
}
break;
case TargetType::AsyncReplicaSet:
throw shcore::Exception::runtime_error(
"The instance '" + instance->descr() +
"' is already part of an InnoDB ReplicaSet");
break;
case TargetType::AsyncReplication:
throw shcore::Exception::runtime_error(
"The instance '" + instance->descr() +
"' is already part of another Asynchronous Replication Topology");
break;
default:
throw shcore::Exception::runtime_error(
"The instance '" + instance->descr() +
"' is already part of another Replication Group");
break;
}
}
void ensure_instance_not_belong_to_metadata(
const mysqlshdk::mysql::IInstance &instance,
const std::string &address_in_metadata,
const mysqlsh::dba::Cluster_impl &cluster) {
auto console = mysqlsh::current_console();
// Check if the instance exists on the cluster
log_debug("Checking if the instance belongs to the cluster");
Instance_metadata instance_md;
try {
instance_md = cluster.get_metadata_storage()->get_instance_by_address(
address_in_metadata);
} catch (const shcore::Exception &e) {
if (e.code() == SHERR_DBA_MEMBER_METADATA_MISSING) {
return;
}
throw;
}
if (instance_md.cluster_id == cluster.get_id()) {
// Check if instance is running auto-rejoin
bool is_rejoining = mysqlshdk::gr::is_running_gr_auto_rejoin(instance);
std::string err_msg = "The instance '" + instance.descr() +
"' already belongs to the cluster: '" +
cluster.get_name() + "'";
if (is_rejoining) {
err_msg += " and is currently trying to auto-rejoin.";
} else {
err_msg += ".";
}
throw shcore::Exception::runtime_error(err_msg);
}
}
size_t check_illegal_async_channels(
const mysqlshdk::mysql::IInstance &instance,
const std::unordered_set<std::string> &allowed_channels_) {
auto console = mysqlsh::current_console();
std::unordered_set<std::string> allowed_channels(allowed_channels_);
allowed_channels.insert("group_replication_applier");
allowed_channels.insert("group_replication_recovery");
allowed_channels.insert("mysqlsh.test");
auto channels = get_incoming_channels(instance);
size_t illegal_channels = channels.size();
for (const auto &ch : channels) {
if (std::find(allowed_channels.begin(), allowed_channels.end(),
ch.channel_name) != allowed_channels.end())
illegal_channels--;
else
console->print_note("Found unexpected replication channel '" +
ch.channel_name + "' at " + instance.descr());
}
return illegal_channels;
}
/**
* Validate if asynchronous replication is configured on the target instance.
*
* This function validates if replication is configured on the target
* instance and depending on the type of check, prints a warning message,
* or an error message and terminates with an exception.
*
* @param instance The instance to validate.
* @param allowed_channels List of channel names that can appear at the
* instance
* @param type The type of check (Check_type)
*/
void validate_async_channels(
const mysqlshdk::mysql::IInstance &instance,
const std::unordered_set<std::string> &allowed_channels, Check_type type) {
log_debug(
"Checking if instance '%s' has asynchronous (source-replica) "
"replication configured.",
instance.descr().c_str());
auto console = mysqlsh::current_console();
size_t illegal_channels =
check_illegal_async_channels(instance, allowed_channels);
if (illegal_channels > 0) {
std::string error_msg;
switch (type) {
case Check_type::CHECK:
error_msg += "The";
break;
case Check_type::CREATE:
error_msg += "Cannot create cluster on";
break;
case Check_type::BOOTSTRAP:
error_msg += "Cannot bootstrap cluster on";
break;
case Check_type::JOIN:
error_msg += "Cannot join";
break;
case Check_type::REJOIN:
error_msg += "Cannot rejoin";
break;
}
error_msg +=
" instance '" + instance.descr() + "' " +
std::string((type == Check_type::JOIN || type == Check_type::REJOIN)
? "to the cluster "
: "") +
std::string(type == Check_type::CHECK
? "cannot be added to an InnoDB cluster "
: "") +
"because it has asynchronous (source-replica) "
"replication channel(s) configured. "
"MySQL InnoDB Cluster does not support manually configured channels "
"as they are not managed using the AdminAPI (e.g. when PRIMARY moves "
"to another member) which may cause cause replication to break or "
"even create split-brain scenarios (data loss).";
if (type == Check_type::CREATE) {
error_msg +=
" Use the 'force' option to skip this validation on a temporary "
"scenario (e.g. migrating from a replication topology to InnoDB "
"Cluster).";
}
if (type == Check_type::CHECK) {
console->print_warning(error_msg);
} else {
console->print_error(error_msg);
throw shcore::Exception::runtime_error(
"The instance '" + instance.descr() +
"' has asynchronous replication configured.");
}
}
}
} // namespace checks
} // namespace dba
} // namespace mysqlsh