diff --git a/mysql-test/suite/clone/r/plugin_mismatch.result b/mysql-test/suite/clone/r/plugin_mismatch.result new file mode 100644 index 000000000000..1d4a99ab75a8 --- /dev/null +++ b/mysql-test/suite/clone/r/plugin_mismatch.result @@ -0,0 +1,39 @@ +# +# PS-8188 : Make the clone_plugin to not force plugins to match on recipient/donor +# +# Install the plugins that will be active and present on donor but +# not installed on recipient +INSTALL PLUGIN example SONAME 'ha_example.so'; +INSTALL PLUGIN version_tokens SONAME 'version_token.so';; + +# create a different plugin_dir for recipient without the example and +# audit_log plugin. Now the recipient server do not have these plugins +# and we do a clone now from donor which has these plugins installed. +# restart: --plugin_dir=RECIPIENT_NEW_PLUGIN_DIR +#connection donor +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 char(64), FULLTEXT KEY fts_index(col2)); +INSERT INTO t1 VALUES(10, 'clone row 1'); +INSERT INTO t1 VALUES(20, 'clone row 2'); +INSERT INTO t1 VALUES(30, 'clone row 3'); +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +#connection recipient +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SET GLOBAL clone_valid_donor_list = 'HOST:PORT'; +CLONE INSTANCE FROM USER@HOST:PORT IDENTIFIED BY '' DATA DIRECTORY='CLONED_DATADIR'; +ERROR HY000: Clone Donor plugin EXAMPLE is not active in Recipient. +SET GLOBAL clone_exclude_plugins_list='example'; +CLONE INSTANCE FROM USER@HOST:PORT IDENTIFIED BY '' DATA DIRECTORY='CLONED_DATADIR'; +ERROR HY000: Clone Donor plugin version_tokens is not active in Recipient. +SET GLOBAL clone_exclude_plugins_list='example,version_tokens'; +CLONE INSTANCE FROM USER@HOST:PORT IDENTIFIED BY '' DATA DIRECTORY='CLONED_DATADIR'; +call mtr.add_suppression("\\[Warning\\] .*MY-\\d+.* Couldn't load plugin named '.*' with soname '.*'."); +call mtr.add_suppression("\\[ERROR\\] .*MY-\\d+.* Can't open shared library.*"); +# restart: --datadir=CLONED_DATADIR --plugin_dir=RECIPIENT_NEW_PLUGIN_DIR +# restart with default datadir +# restart: +UNINSTALL PLUGIN clone; +UNINSTALL PLUGIN version_tokens; +UNINSTALL PLUGIN clone; +UNINSTALL PLUGIN example; +DROP TABLE t1; +# restart: diff --git a/mysql-test/suite/clone/r/plugin_mismatch_variable.result b/mysql-test/suite/clone/r/plugin_mismatch_variable.result new file mode 100644 index 000000000000..fc3e65f011a0 --- /dev/null +++ b/mysql-test/suite/clone/r/plugin_mismatch_variable.result @@ -0,0 +1,52 @@ +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +# test with empty value +SET GLOBAL clone_exclude_plugins_list=''; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list +# test without GLOBAL +SET clone_exclude_plugins_list=''; +ERROR HY000: Variable 'clone_exclude_plugins_list' is a GLOBAL variable and should be set with SET GLOBAL +# test with allowed single value +SET GLOBAL clone_exclude_plugins_list='example'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list example +# test allowed plugin names(any names except the disallowed list) +SET GLOBAL clone_exclude_plugins_list='blah1,blah2'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list blah1,blah2 +# test allowed plugin names(any names except the disallowed list) +SET GLOBAL clone_exclude_plugins_list='example,audit_log'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list example,audit_log +# test disallowed plugin names +SET GLOBAL clone_exclude_plugins_list='InnoDB'; +ERROR HY000: Incorrect arguments to Clone: The following plugins cannot be excluded: innodb +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list example,audit_log +SET GLOBAL clone_exclude_plugins_list='example,audit_log,binlog'; +ERROR HY000: Incorrect arguments to Clone: The following plugins cannot be excluded: binlog +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list example,audit_log +# test disallowed plugin names +SET GLOBAL clone_exclude_plugins_list='InnoDB,Memory,peformance_schema,keyring_file'; +ERROR HY000: Incorrect arguments to Clone: The following plugins cannot be excluded: innodb,memory,keyring_file +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list example,audit_log +# test with SET PERSIST +SET PERSIST clone_exclude_plugins_list='example,audit_log'; +# restart +# After restart, it should show example, audit_log +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +Variable_name Value +clone_exclude_plugins_list example,audit_log +RESET PERSIST; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/plugin_mismatch.cnf b/mysql-test/suite/clone/t/plugin_mismatch.cnf new file mode 100644 index 000000000000..b1f0e1dca15d --- /dev/null +++ b/mysql-test/suite/clone/t/plugin_mismatch.cnf @@ -0,0 +1,14 @@ +# Test remote clone command to replace data directory + +[mysqld.1] +server_id=1 + +[mysqld.2] +server_id=2 + +[ENV] +SERVER_PORT_1 = @mysqld.1.port +SERVER_SOCK_1 = @mysqld.1.socket + +SERVER_PORT_2 = @mysqld.2.port +SERVER_SOCK_2 = @mysqld.2.socket diff --git a/mysql-test/suite/clone/t/plugin_mismatch.test b/mysql-test/suite/clone/t/plugin_mismatch.test new file mode 100644 index 000000000000..f8acc108cea7 --- /dev/null +++ b/mysql-test/suite/clone/t/plugin_mismatch.test @@ -0,0 +1,105 @@ +--source include/have_example_plugin.inc + +--echo # +--echo # PS-8188 : Make the clone_plugin to not force plugins to match on recipient/donor +--echo # + +--let $HOST = 127.0.0.1 +--let $PORT =`select @@port` +--let $USER = root + +--echo # Install the plugins that will be active and present on donor but +--echo # not installed on recipient + +--eval INSTALL PLUGIN example SONAME '$EXAMPLE_PLUGIN' +--eval INSTALL PLUGIN version_tokens SONAME '$VERSION_TOKEN'; + +--let $recipient_port= \$SERVER_PORT_2 +--connect (conn_recipient, 127.0.0.1, root,,test,$recipient_port) +--connection conn_recipient + +--echo +--echo # create a different plugin_dir for recipient without the example and +--echo # audit_log plugin. Now the recipient server do not have these plugins +--echo # and we do a clone now from donor which has these plugins installed. + +--mkdir $MYSQL_TMP_DIR/plugin_dir +--let $plugin_dir=`SELECT @@plugin_dir` +--let $RECIPIENT_NEW_PLUGIN_DIR=$MYSQL_TMP_DIR/plugin_dir + +# Copy only mysql_clone plugin that is required for the test. +--copy_file $plugin_dir/mysql_clone.so $RECIPIENT_NEW_PLUGIN_DIR/mysql_clone.so + +--replace_result $RECIPIENT_NEW_PLUGIN_DIR RECIPIENT_NEW_PLUGIN_DIR +--let $restart_parameters="restart: --plugin_dir=$RECIPIENT_NEW_PLUGIN_DIR" +--source include/restart_mysqld.inc +--let $restart_parameters= + +--echo #connection donor +--connection default +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 char(64), FULLTEXT KEY fts_index(col2)); + +INSERT INTO t1 VALUES(10, 'clone row 1'); +INSERT INTO t1 VALUES(20, 'clone row 2'); +INSERT INTO t1 VALUES(30, 'clone row 3'); + +--let $checksum_table_donor=query_get_value(CHECKSUM TABLE t1, Checksum, 1) + +--replace_result $CLONE_PLUGIN CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$CLONE_PLUGIN' + +--echo #connection recipient +--connection conn_recipient +--replace_result $CLONE_PLUGIN CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$CLONE_PLUGIN' +--replace_result $HOST HOST $PORT PORT +--eval SET GLOBAL clone_valid_donor_list = '$HOST:$PORT' + +--let $CLONED_DATADIR=$MYSQL_TMP_DIR/cloned_data_dir +--replace_result $CLONED_DATADIR CLONED_DATADIR $USER USER $HOST HOST $PORT PORT +--error ER_CLONE_PLUGIN_MATCH +--eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' DATA DIRECTORY='$CLONED_DATADIR' + +SET GLOBAL clone_exclude_plugins_list='example'; +--replace_result $CLONED_DATADIR CLONED_DATADIR $USER USER $HOST HOST $PORT PORT +--error ER_CLONE_PLUGIN_MATCH +--eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' DATA DIRECTORY='$CLONED_DATADIR' + +SET GLOBAL clone_exclude_plugins_list='example,version_tokens'; +--replace_result $CLONED_DATADIR CLONED_DATADIR $USER USER $HOST HOST $PORT PORT +--eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' DATA DIRECTORY='$CLONED_DATADIR' + +--connection conn_recipient +# Restart server on cloned data directory + +call mtr.add_suppression("\\[Warning\\] .*MY-\\d+.* Couldn't load plugin named '.*' with soname '.*'."); +call mtr.add_suppression("\\[ERROR\\] .*MY-\\d+.* Can't open shared library.*"); + +--replace_result $CLONED_DATADIR CLONED_DATADIR $RECIPIENT_NEW_PLUGIN_DIR RECIPIENT_NEW_PLUGIN_DIR +--let $restart_parameters="restart: --datadir=$CLONED_DATADIR --plugin_dir=$RECIPIENT_NEW_PLUGIN_DIR" +--source include/restart_mysqld.inc +--connection conn_recipient + +--let $checksum_table_recipient=query_get_value(CHECKSUM TABLE t1, Checksum, 1) +--assert($checksum_table_donor == $checksum_table_recipient) + +--echo # restart with default datadir +--let $restart_parameters="restart:" +--source include/restart_mysqld.inc +--connection conn_recipient + +--source include/count_sessions.inc +UNINSTALL PLUGIN clone; + +--connection default +--disable_warnings +UNINSTALL PLUGIN version_tokens; +UNINSTALL PLUGIN clone; +UNINSTALL PLUGIN example; +--enable_warnings +DROP TABLE t1; +--disconnect conn_recipient +--source include/wait_until_count_sessions.inc +--force-rmdir $CLONED_DATADIR +--force-rmdir $MYSQL_TMP_DIR/plugin_dir +--source include/restart_mysqld.inc diff --git a/mysql-test/suite/clone/t/plugin_mismatch_variable.test b/mysql-test/suite/clone/t/plugin_mismatch_variable.test new file mode 100644 index 000000000000..148e20a7f653 --- /dev/null +++ b/mysql-test/suite/clone/t/plugin_mismatch_variable.test @@ -0,0 +1,47 @@ +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--replace_result $CLONE_PLUGIN CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$CLONE_PLUGIN' + +--echo # test with empty value +SET GLOBAL clone_exclude_plugins_list=''; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--echo # test without GLOBAL +--error ER_GLOBAL_VARIABLE +SET clone_exclude_plugins_list=''; + +--echo # test with allowed single value +SET GLOBAL clone_exclude_plugins_list='example'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--echo # test allowed plugin names(any names except the disallowed list) +SET GLOBAL clone_exclude_plugins_list='blah1,blah2'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--echo # test allowed plugin names(any names except the disallowed list) +SET GLOBAL clone_exclude_plugins_list='example,audit_log'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--echo # test disallowed plugin names +--error ER_WRONG_ARGUMENTS +SET GLOBAL clone_exclude_plugins_list='InnoDB'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--error ER_WRONG_ARGUMENTS +SET GLOBAL clone_exclude_plugins_list='example,audit_log,binlog'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--echo # test disallowed plugin names +--error ER_WRONG_ARGUMENTS +SET GLOBAL clone_exclude_plugins_list='InnoDB,Memory,peformance_schema,keyring_file'; +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; + +--echo # test with SET PERSIST +SET PERSIST clone_exclude_plugins_list='example,audit_log'; +--source include/restart_mysqld.inc +--echo # After restart, it should show example, audit_log +SHOW VARIABLES LIKE 'clone_exclude_plugins_list'; +RESET PERSIST; + +UNINSTALL PLUGIN clone; diff --git a/plugin/clone/include/clone.h b/plugin/clone/include/clone.h index 0d454da3b05c..f28de50b5a82 100644 --- a/plugin/clone/include/clone.h +++ b/plugin/clone/include/clone.h @@ -119,6 +119,8 @@ const uint CLONE_MIN_BLOCK = 1024 * 1024; /** Minimum network packet. Safe margin for meta information */ const uint CLONE_MIN_NET_BLOCK = 2 * CLONE_MIN_BLOCK; +extern char *clone_exclude_plugins_list; + /** Clone supported compression libs */ #define CLONE_COMPRESSION_ALGORITHM_COUNT_MAX 3 extern const char diff --git a/plugin/clone/src/clone_client.cc b/plugin/clone/src/clone_client.cc index 0a0043706d75..f1fb95974474 100644 --- a/plugin/clone/src/clone_client.cc +++ b/plugin/clone/src/clone_client.cc @@ -28,6 +28,7 @@ Clone Plugin: Client implementation */ #include +#include "plugin/clone/include/clone.h" #include "plugin/clone/include/clone_client.h" #include "plugin/clone/include/clone_os.h" @@ -1051,6 +1052,26 @@ int Client::validate_remote_params() { last_error = ER_CLONE_PLUGIN_MATCH; } + /* Build list of plugins from comma separated value of the variable + clone_exclude_plugins_list */ + std::vector excl_plugins_vec; + if (clone_exclude_plugins_list != nullptr) { + std::string excl_plugins_str(clone_exclude_plugins_list); + std::transform(excl_plugins_str.begin(), excl_plugins_str.end(), + excl_plugins_str.begin(), ::tolower); + + std::stringstream exclude_stream(excl_plugins_str); + + while (exclude_stream.good()) { + std::string substr; + getline(exclude_stream, substr, ','); + substr.erase(remove(substr.begin(), substr.end(), ' '), substr.end()); + if (!substr.empty()) { + excl_plugins_vec.push_back(substr); + } + } + } + /* Validate plugins and check if shared objects can be loaded. */ for (auto &plugin : m_parameters.m_plugins_with_so) { assert(m_share->m_protocol_version > CLONE_PROTOCOL_VERSION_V1); @@ -1080,6 +1101,26 @@ int Client::validate_remote_params() { continue; } + /* Plugin is not installed in recipient but active on donor. Check if this + plugin can be exempted from matching with donor */ + if (clone_exclude_plugins_list != nullptr) { + std::string plugin_str(plugin_name); + std::transform(plugin_str.begin(), plugin_str.end(), plugin_str.begin(), + ::tolower); + + if (std::find(excl_plugins_vec.begin(), excl_plugins_vec.end(), + plugin_str) != excl_plugins_vec.end()) { + char info_mesg[256]; + snprintf(info_mesg, 256, + "Ignoring plugin %s installed on the source server but not on " + "the recepient. The mismatch is ignored due to " + "'clone_exclude_plugins_list'.", + plugin_name.c_str()); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + continue; + } + } + /* Donor plugin is not there in recipient. */ my_error(ER_CLONE_PLUGIN_MATCH, MYF(0), plugin_name.c_str()); last_error = ER_CLONE_PLUGIN_MATCH; diff --git a/plugin/clone/src/clone_plugin.cc b/plugin/clone/src/clone_plugin.cc index c6d58c3fc554..2dba57e3f6c9 100644 --- a/plugin/clone/src/clone_plugin.cc +++ b/plugin/clone/src/clone_plugin.cc @@ -30,10 +30,12 @@ Clone Plugin: Plugin interface #include #include +#include "include/sql_string.h" #include "plugin/clone/include/clone_client.h" #include "plugin/clone/include/clone_local.h" #include "plugin/clone/include/clone_server.h" #include "plugin/clone/include/clone_status.h" +#include "sql/sql_error.h" #include #include @@ -90,6 +92,19 @@ uint clone_restart_timeout; /** Clone system variable: time delay after removing data */ uint clone_delay_after_data_drop; +/** Clone system variable: list of plugins that will not be matched on +recipient */ +char *clone_exclude_plugins_list; + +/** List of plugins that cannot be excluded by clone_exclude_plugins_list */ +static std::vector disallow_list{"daemon_keyring_proxy_plugin", + "binlog", + "performance_schema", + "memory", + "innodb", + "keyring_file", + "keyring_vault"}; + /** Key for registering clone allocations with performance schema */ PSI_memory_key clone_mem_key; @@ -510,6 +525,62 @@ static int plugin_clone_remote_server(THD *thd, MYSQL_SOCKET socket) { return (err); } +static const char *val_strmake(MYSQL_THD thd, + struct st_mysql_value *mysql_val) { + char buf[STRING_BUFFER_USUAL_SIZE]; + int len = sizeof(buf); + const char *val = mysql_val->val_str(mysql_val, buf, &len); + + if (val != NULL) val = thd_strmake(thd, val, len); + + return val; +} + +static bool plugin_is_ignorable(std::string const &plugin_name) { + return (std::find(disallow_list.begin(), disallow_list.end(), plugin_name) == + disallow_list.end()); +} + +static int clone_exclude_plugins_list_validate(MYSQL_THD thd, + SYS_VAR *var [[maybe_unused]], + void *save, + st_mysql_value *value) { + const char *input = val_strmake(thd, value); + std::stringstream exclude_list(input); + + // Disallow InnoDB, keyring_*, GR plugins to be excluded + + std::ostringstream err_strm; + err_strm << "Clone: The following plugins cannot be excluded: "; + bool bad = false; + while (exclude_list.good()) { + std::string substr; + getline(exclude_list, substr, ','); + // remove all spaces in string + substr.erase(remove(substr.begin(), substr.end(), ' '), substr.end()); + if (substr.empty()) continue; + + std::transform(substr.begin(), substr.end(), substr.begin(), ::tolower); + if (!plugin_is_ignorable(substr)) { + err_strm << substr << ","; + bad = true; + } + } + + if (bad) { + std::string error(err_strm.str()); + error.erase(remove(error.end() - 1, error.end(), ','), error.end()); + + my_error(ER_WRONG_ARGUMENTS, MYF(0), error.c_str()); + + *static_cast(save) = nullptr; + return 1; + } + + *static_cast(save) = input; + return 0; +} + /** clone plugin interfaces */ struct Mysql_clone clone_descriptor = { MYSQL_CLONE_INTERFACE_VERSION, plugin_clone_local, @@ -655,6 +726,16 @@ static MYSQL_SYSVAR_UINT(delay_after_data_drop, clone_delay_after_data_drop, 60 * 60, /* Maximum = 1 hour */ 1); /* Step = 1 sec */ +/** Remote cloning insists on the same list of plugins to be installed on +recipient. These list of plugins are not required to be installed on recipient. +*/ +static MYSQL_SYSVAR_STR(exclude_plugins_list, clone_exclude_plugins_list, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + "Comma separated plugin names that are not installed " + "on recipient. Clone will not error out if these are " + "active on donor but not installed on recipient", + clone_exclude_plugins_list_validate, nullptr, nullptr); + /** Clone system variables */ static SYS_VAR *clone_system_variables[] = { MYSQL_SYSVAR(buffer_size), @@ -671,6 +752,7 @@ static SYS_VAR *clone_system_variables[] = { MYSQL_SYSVAR(ssl_ca), MYSQL_SYSVAR(donor_timeout_after_network_failure), MYSQL_SYSVAR(delay_after_data_drop), + MYSQL_SYSVAR(exclude_plugins_list), MYSQL_SYSVAR(compression_algorithm), MYSQL_SYSVAR(zstd_compression_level), nullptr};