MXS-1845 Complete server promotion code

Now copies slave connections from the previous master. Promotion
code taken into use.
This commit is contained in:
Esa Korhonen
2018-09-27 10:17:15 +03:00
parent dbbadf1f53
commit 6b8443aba6
5 changed files with 330 additions and 32 deletions

View File

@ -711,7 +711,7 @@ bool MariaDBMonitor::switchover_perform(ClusterOperation& op)
op.time_remaining -= step3_duration; op.time_remaining -= step3_duration;
// Step 4: On new master STOP and RESET SLAVE, set read-only to off. // Step 4: On new master STOP and RESET SLAVE, set read-only to off.
if (promote_new_master(promotion_target, error_out)) if (promotion_target->promote(op))
{ {
catchup_and_promote_success = true; catchup_and_promote_success = true;
m_next_master = promotion_target; m_next_master = promotion_target;
@ -800,7 +800,7 @@ bool MariaDBMonitor::failover_perform(ClusterOperation& op)
bool rval = false; bool rval = false;
// Step 2: Stop and reset slave, set read-only to 0. // Step 2: Stop and reset slave, set read-only to 0.
if (promote_new_master(promotion_target, op.error_out)) if (promotion_target->promote(op))
{ {
m_next_master = promotion_target; m_next_master = promotion_target;
m_cluster_modified = true; m_cluster_modified = true;
@ -1509,6 +1509,7 @@ unique_ptr<ClusterOperation> MariaDBMonitor::failover_prepare(Log log_mode, json
promotion_target, demotion_target, promotion_target, demotion_target,
demotion_target == m_master, m_handle_event_scheduler, demotion_target == m_master, m_handle_event_scheduler,
m_promote_sql_file, m_demote_sql_file, m_promote_sql_file, m_demote_sql_file,
m_replication_user, m_replication_password,
error_out, time_limit)); error_out, time_limit));
} }
} }
@ -1849,6 +1850,7 @@ unique_ptr<ClusterOperation> MariaDBMonitor::switchover_prepare(SERVER* promotio
promotion_target, demotion_target, promotion_target, demotion_target,
demotion_target == m_master, m_handle_event_scheduler, demotion_target == m_master, m_handle_event_scheduler,
m_promote_sql_file, m_demote_sql_file, m_promote_sql_file, m_demote_sql_file,
m_replication_user, m_replication_password,
error_out, time_limit)); error_out, time_limit));
} }
return rval; return rval;

View File

@ -37,6 +37,7 @@ ClusterOperation::ClusterOperation(OperationType type,
MariaDBServer* promotion_target, MariaDBServer* demotion_target, MariaDBServer* promotion_target, MariaDBServer* demotion_target,
bool demo_target_is_master, bool handle_events, bool demo_target_is_master, bool handle_events,
string& promotion_sql_file, string& demotion_sql_file, string& promotion_sql_file, string& demotion_sql_file,
string& replication_user, string& replication_password,
json_t** error, maxbase::Duration time_remaining) json_t** error, maxbase::Duration time_remaining)
: type(type) : type(type)
, promotion_target(promotion_target) , promotion_target(promotion_target)
@ -45,6 +46,8 @@ ClusterOperation::ClusterOperation(OperationType type,
, handle_events(handle_events) , handle_events(handle_events)
, promotion_sql_file(promotion_sql_file) , promotion_sql_file(promotion_sql_file)
, demotion_sql_file(demotion_sql_file) , demotion_sql_file(demotion_sql_file)
, replication_user(replication_user)
, replication_password(replication_password)
, error_out(error) , error_out(error)
, time_remaining(time_remaining) , time_remaining(time_remaining)
{} {}

View File

@ -88,6 +88,10 @@ class MariaDBServer;
*/ */
class ClusterOperation class ClusterOperation
{ {
private:
ClusterOperation(const ClusterOperation&) = delete;
ClusterOperation& operator=(const ClusterOperation&) = delete;
public: public:
const OperationType type; // Failover or switchover const OperationType type; // Failover or switchover
MariaDBServer* const promotion_target; // Which server will be promoted MariaDBServer* const promotion_target; // Which server will be promoted
@ -96,6 +100,8 @@ public:
const bool handle_events; // Should scheduled server events be disabled/enabled? const bool handle_events; // Should scheduled server events be disabled/enabled?
const std::string promotion_sql_file; // SQL commands ran on a server promoted to master const std::string promotion_sql_file; // SQL commands ran on a server promoted to master
const std::string demotion_sql_file; // SQL commands ran on a server demoted from master const std::string demotion_sql_file; // SQL commands ran on a server demoted from master
const std::string replication_user; // User for CHANGE MASTER TO ...
const std::string replication_password; // Password for CHANGE MASTER TO ...
json_t** const error_out; // Json error output json_t** const error_out; // Json error output
maxbase::Duration time_remaining; // How much time remains to complete the operation maxbase::Duration time_remaining; // How much time remains to complete the operation
@ -103,5 +109,6 @@ public:
MariaDBServer* promotion_target, MariaDBServer* demotion_target, MariaDBServer* promotion_target, MariaDBServer* demotion_target,
bool demo_target_is_master, bool handle_events, bool demo_target_is_master, bool handle_events,
std::string& promotion_sql_file, std::string& demotion_sql_file, std::string& promotion_sql_file, std::string& demotion_sql_file,
std::string& replication_user, std::string& replication_password,
json_t** error, maxbase::Duration time_remaining); json_t** error, maxbase::Duration time_remaining);
}; };

View File

@ -19,6 +19,7 @@
#include <thread> #include <thread>
#include <maxscale/mysql_utils.h> #include <maxscale/mysql_utils.h>
#include <maxscale/utils.hh> #include <maxscale/utils.hh>
#include <set>
using std::string; using std::string;
using maxscale::string_printf; using maxscale::string_printf;
@ -1178,6 +1179,14 @@ const SlaveStatus* MariaDBServer::slave_connection_status(const MariaDBServer* t
return rval; return rval;
} }
/**
* Private, non-const version of 'slave_connection_status'.
*/
SlaveStatus* MariaDBServer::slave_connection_status_mutable(const MariaDBServer* target)
{
return const_cast<SlaveStatus*>(slave_connection_status(target));
}
bool MariaDBServer::enable_events(json_t** error_out) bool MariaDBServer::enable_events(json_t** error_out)
{ {
int found_disabled_events = 0; int found_disabled_events = 0;
@ -1417,48 +1426,46 @@ bool MariaDBServer::reset_all_slave_conns(json_t** error_out)
return !error; return !error;
} }
bool MariaDBServer::promote_v2(ClusterOperation* op) bool MariaDBServer::promote(ClusterOperation& op)
{ {
bool success = false; json_t** const error_out = op.error_out;
json_t** const error_out = op->error_out;
// Function should only be called for a master-slave pair. // Function should only be called for a master-slave pair.
auto master_conn = slave_connection_status(op->demotion_target); auto master_conn = slave_connection_status_mutable(op.demotion_target);
mxb_assert(master_conn); mxb_assert(master_conn);
if (master_conn == NULL) if (master_conn == NULL)
{ {
PRINT_MXS_JSON_ERROR(error_out, PRINT_MXS_JSON_ERROR(error_out,
"'%s' is not a slave of '%s' and cannot be promoted to its place.", "'%s' is not a slave of '%s' and cannot be promoted to its place.",
name(), op->demotion_target->name()); name(), op.demotion_target->name());
return false; return false;
} }
bool success = false;
StopWatch timer; StopWatch timer;
// Step 1: Stop & reset slave connections. If doing a failover, only remove the connection to demotion // Step 1: Stop & reset slave connections. If doing a failover, only remove the connection to demotion
// target. In case of switchover, remove other slave connections as well since the demotion target // target. In case of switchover, remove other slave connections as well since the demotion target
// will take them over. // will take them over.
bool stop_slave_error = false; bool stop_slave_error = false;
// Helper function for stopping a slave connection and setting error. // Helper function for stopping a slave connection and setting error.
auto stop_slave_helper = [this, &timer, &stop_slave_error, op, error_out](const string& conn_name) { auto stop_slave_helper = [this, &timer, &stop_slave_error, &op, error_out](SlaveStatus* slave_conn) {
if (!stop_slave_conn(conn_name, StopMode::RESET_ALL, op->time_remaining, error_out)) if (!stop_slave_conn(slave_conn, StopMode::RESET_ALL, op.time_remaining, error_out))
{ {
stop_slave_error = true; stop_slave_error = true;
} }
op->time_remaining -= timer.restart(); op.time_remaining -= timer.restart();
}; };
if (op->type == OperationType::SWITCHOVER) if (op.type == OperationType::SWITCHOVER)
{ {
for (size_t i = 0; !stop_slave_error && i < m_slave_status.size(); i++) for (size_t i = 0; !stop_slave_error && i < m_slave_status.size(); i++)
{ {
stop_slave_helper(m_slave_status[i].name); stop_slave_helper(&m_slave_status[i]);
} }
} }
else else
{ {
stop_slave_helper(master_conn->name); stop_slave_helper(master_conn);
} }
if (!stop_slave_error) if (!stop_slave_error)
@ -1466,22 +1473,22 @@ bool MariaDBServer::promote_v2(ClusterOperation* op)
// Step 2: If demotion target is master, meaning this server will become the master, // Step 2: If demotion target is master, meaning this server will become the master,
// enable writing and scheduled events. Also, run promotion_sql_file. // enable writing and scheduled events. Also, run promotion_sql_file.
bool promotion_error = false; bool promotion_error = false;
if (op->demotion_target_is_master) if (op.demotion_target_is_master)
{ {
// Disabling read-only should be quick. // Disabling read-only should be quick.
bool ro_disabled = set_read_only(ReadOnlySetting::DISABLE, op->time_remaining, error_out); bool ro_disabled = set_read_only(ReadOnlySetting::DISABLE, op.time_remaining, error_out);
op->time_remaining -= timer.restart(); op.time_remaining -= timer.restart();
if (!ro_disabled) if (!ro_disabled)
{ {
promotion_error = true; promotion_error = true;
} }
else else
{ {
if (op->handle_events) if (op.handle_events)
{ {
// TODO: Add query replying to enable_events // TODO: Add query replying to enable_events
bool events_enabled = enable_events(error_out); bool events_enabled = enable_events(error_out);
op->time_remaining -= timer.restart(); op.time_remaining -= timer.restart();
if (!events_enabled) if (!events_enabled)
{ {
promotion_error = true; promotion_error = true;
@ -1490,16 +1497,16 @@ bool MariaDBServer::promote_v2(ClusterOperation* op)
} }
// Run promotion_sql_file if no errors so far. // Run promotion_sql_file if no errors so far.
if (!promotion_error && !op->promotion_sql_file.empty()) if (!promotion_error && !op.promotion_sql_file.empty())
{ {
bool file_ran_ok = run_sql_from_file(op->promotion_sql_file, error_out); bool file_ran_ok = run_sql_from_file(op.promotion_sql_file, error_out);
op->time_remaining -= timer.restart(); op.time_remaining -= timer.restart();
if (!file_ran_ok) if (!file_ran_ok)
{ {
promotion_error = true; promotion_error = true;
PRINT_MXS_JSON_ERROR(error_out, PRINT_MXS_JSON_ERROR(error_out,
"Execution of file '%s' failed during promotion of server '%s'.", "Execution of file '%s' failed during promotion of server '%s'.",
op->promotion_sql_file.c_str(), name()); op.promotion_sql_file.c_str(), name());
} }
} }
} }
@ -1507,16 +1514,25 @@ bool MariaDBServer::promote_v2(ClusterOperation* op)
// Step 3: Copy slave connections from demotion target. If demotion target was replicating from // Step 3: Copy slave connections from demotion target. If demotion target was replicating from
// this server (circular topology), the connection should be ignored. Also, connections which // this server (circular topology), the connection should be ignored. Also, connections which
// already exist on this server need not be regenerated. // already exist on this server (when failovering) need not be regenerated.
if (!promotion_error) if (!promotion_error)
{ {
// TODO if (copy_master_slave_conns(op)) // The method updates 'time_remaining' itself.
{
success = true;
}
else
{
PRINT_MXS_JSON_ERROR(error_out,
"Could not copy slave connections from '%s' to '%s'.",
op.demotion_target->name(), name());
}
} }
} }
return success; return success;
} }
bool MariaDBServer::stop_slave_conn(const string& conn_name, StopMode mode, Duration time_limit, bool MariaDBServer::stop_slave_conn(SlaveStatus* slave_conn, StopMode mode, Duration time_limit,
json_t** error_out) json_t** error_out)
{ {
/* STOP SLAVE is a bit problematic, since sometimes it seems to take several seconds to complete. /* STOP SLAVE is a bit problematic, since sometimes it seems to take several seconds to complete.
@ -1526,7 +1542,8 @@ bool MariaDBServer::stop_slave_conn(const string& conn_name, StopMode mode, Dura
* an already stopped slave connection an error. */ * an already stopped slave connection an error. */
Duration time_left = time_limit; Duration time_left = time_limit;
StopWatch timer; StopWatch timer;
string stop = string_printf("STOP SLAVE '%s';", conn_name.c_str()); const char* conn_name = slave_conn->name.c_str();
string stop = string_printf("STOP SLAVE '%s';", conn_name);
string error_msg; string error_msg;
bool stop_success = execute_cmd_time_limit(stop, time_left, &error_msg); bool stop_success = execute_cmd_time_limit(stop, time_left, &error_msg);
time_left -= timer.restart(); time_left -= timer.restart();
@ -1539,10 +1556,15 @@ bool MariaDBServer::stop_slave_conn(const string& conn_name, StopMode mode, Dura
if (mode == StopMode::RESET || mode == StopMode::RESET_ALL) if (mode == StopMode::RESET || mode == StopMode::RESET_ALL)
{ {
string reset = string_printf("RESET SLAVE '%s'%s;", string reset = string_printf("RESET SLAVE '%s'%s;",
conn_name.c_str(), (mode == StopMode::RESET_ALL) ? " ALL" : ""); conn_name, (mode == StopMode::RESET_ALL) ? " ALL" : "");
if (execute_cmd_time_limit(reset, time_left, &error_msg)) if (execute_cmd_time_limit(reset, time_left, &error_msg))
{ {
rval = true; rval = true;
if (mode == StopMode::RESET_ALL)
{
slave_conn->exists = false; // The RESET_ALL means that the connection has been
// erased.
}
} }
else else
{ {
@ -1581,6 +1603,249 @@ bool MariaDBServer::set_read_only(ReadOnlySetting setting, maxbase::Duration tim
return success; return success;
} }
/**
* Copy slave connections from the demotion target to this server.
*
* @param op Operation descriptor
* @return True on success
*/
bool MariaDBServer::copy_master_slave_conns(ClusterOperation& op)
{
mxb_assert(this == op.promotion_target);
// Helper function for checking if a slave connection should be ignored.
auto should_ignore_connection =
[this](const SlaveStatus& slave_conn, OperationType type, string* ignore_reason_out) -> bool {
bool conn_ignored = false;
auto master_id = slave_conn.master_server_id;
// The connection is only copied if it is running or at least has been seen running.
// Also, target should not be this server.
string ignore_reason;
if (!slave_conn.slave_sql_running)
{
conn_ignored = true;
ignore_reason = "its slave sql thread is not running.";
}
else if (!slave_conn.seen_connected)
{
conn_ignored = true;
ignore_reason = "it has not been seen connected to master.";
}
else if (master_id <= 0)
{
conn_ignored = true;
ignore_reason = string_printf("its Master_Server_Id (%" PRIi64 ") is invalid .", master_id);
}
else if (master_id == m_server_id)
{
// This is not really an error but indicates a complicated topology.
conn_ignored = true;
ignore_reason = "it points to the server being promoted (according to server id:s).";
}
else if (type == OperationType::FAILOVER)
{
/* In the case of failover, not all connections have been removed from this server
* (promotion_target). The promotion and demotion targets may have identical connections
* (connections going to the same server id or the same host:port). These connections are
* ignored when copying slave connections. It's possible that the master had different
* settings for a duplicate slave connection, in this case the settings on the master are
* lost. */
/* This check should not be done in the case of switchover because while the connections
* have been removed on the real server (this), they are still present in the MariaDBServer
* object and could cause false detections. */
for (const SlaveStatus& my_slave_conn : m_slave_status)
{
if (my_slave_conn.seen_connected
&& my_slave_conn.master_server_id == slave_conn.master_server_id)
{
conn_ignored = true;
const char format[] =
"its Master_Server_Id (%" PRIi64 ") matches an existing slave connection on %s.";
ignore_reason = string_printf(format, slave_conn.master_server_id, name());
}
else if (my_slave_conn.master_host == slave_conn.master_host
&& my_slave_conn.master_port == slave_conn.master_port)
{
conn_ignored = true;
ignore_reason = string_printf("its Master_Host (%s) and Master_Port (%i) match "
"an existing slave connection on %s.",
slave_conn.master_host.c_str(), slave_conn.master_port,
name());
}
}
}
if (conn_ignored)
{
*ignore_reason_out = ignore_reason;
}
return conn_ignored;
};
// Need to keep track of recently created connection names to avoid using an existing name.
std::set<string> created_connection_names;
// Helper function which checks that a connection name is unique and modifies it if not.
auto check_modify_conn_name = [this, &created_connection_names](SlaveStatus* slave_conn) -> bool {
// An inner lambda which checks name uniqueness.
auto name_is_used = [this, &created_connection_names](const string& name) -> bool {
if (created_connection_names.count(name) > 0)
{
return true;
}
else
{
for (const auto& slave_conn : m_slave_status)
{
if (slave_conn.exists && slave_conn.name == name)
{
return true;
}
}
}
return false;
};
bool name_is_unique = false;
if (name_is_used(slave_conn->name))
{
// If the name is used, generate a name using the host:port of the master, it should be
// unique.
string second_try = string_printf("To [%s]:%i",
slave_conn->master_host.c_str(), slave_conn->master_port);
if (name_is_used(second_try))
{
// Even this one exists, something is really wrong. Give up.
MXS_ERROR("Could not generate a unique connection name for '%s': both '%s' and '%s' are "
"already taken.", name(), slave_conn->name.c_str(), second_try.c_str());
}
else
{
MXS_WARNING("A slave connection with name '%s' already exists on %s, using generated "
"name '%s' instead.", slave_conn->name.c_str(), name(), second_try.c_str());
slave_conn->name = second_try;
name_is_unique = true;
}
}
else
{
name_is_unique = true;
}
return name_is_unique;
};
bool error = false;
const auto& conns_to_copy = op.demotion_target->m_slave_status;
for (size_t i = 0; !error && (i < conns_to_copy.size()); i++)
{
// Need a copy of the array element here since it may be modified.
SlaveStatus slave_conn = conns_to_copy[i];
string ignore_reason;
if (should_ignore_connection(slave_conn, op.type, &ignore_reason))
{
MXS_WARNING("%s was ignored when promoting %s because %s",
slave_conn.to_short_string(op.demotion_target->name()).c_str(), name(),
ignore_reason.c_str());
}
else
{
/* If doing a switchover, all slave connections of this server should have been removed.
* It's safe to make new ones and connection names can be assumed unique since they come
* from an existing server. */
/* When doing failover, this server may have some of the connections of the demotion target.
* Those connections have already been detected by 'should_ignore_connection', but this
* server may still have connections with identical names. If so, modify the name. */
bool can_continue = true;
if (op.type == OperationType::FAILOVER)
{
can_continue = check_modify_conn_name(&slave_conn);
}
if (can_continue)
{
if (create_start_slave(op, slave_conn))
{
created_connection_names.insert(slave_conn.name);
}
else
{
error = true;
}
}
else
{
error = true;
}
}
}
return !error;
}
/**
* Create a new slave connection on the server and start it.
*
* @param op Operation descriptor
* @param slave_conn Existing connection to emulate
* @return True on success
*/
bool MariaDBServer::create_start_slave(ClusterOperation& op, const SlaveStatus& slave_conn)
{
StopWatch timer;
string error_msg;
bool success = false;
string change_master = generate_change_master_cmd(op, slave_conn);
bool conn_created = execute_cmd_time_limit(change_master, op.time_remaining, &error_msg);
op.time_remaining -= timer.restart();
if (conn_created)
{
string start_slave = string_printf("START SLAVE '%s';", slave_conn.name.c_str());
bool slave_started = execute_cmd_time_limit(start_slave, op.time_remaining, &error_msg);
op.time_remaining -= timer.restart();
if (slave_started)
{
success = true;
MXS_NOTICE("%s created and started.", slave_conn.to_short_string(name()).c_str());
}
else
{
MXS_ERROR("%s could not be started: %s",
slave_conn.to_short_string(name()).c_str(), error_msg.c_str());
}
}
else
{
// TODO: This may currently print out passwords.
MXS_ERROR("%s could not be created: %s",
slave_conn.to_short_string(name()).c_str(), error_msg.c_str());
}
return success;
}
/**
* Generate a CHANGE MASTER TO-query.
*
* @param op Operation descriptor
* @param slave_conn Existing slave connection to emulate
* @return Generated query
*/
string MariaDBServer::generate_change_master_cmd(ClusterOperation& op, const SlaveStatus& slave_conn)
{
string change_cmd;
change_cmd += string_printf("CHANGE MASTER '%s' TO MASTER_HOST = '%s', MASTER_PORT = %i, ",
slave_conn.name.c_str(), slave_conn.master_host.c_str(),
slave_conn.master_port);
change_cmd += "MASTER_USE_GTID = current_pos, ";
change_cmd += string_printf("MASTER_USER = '%s', ", op.replication_user.c_str());
const char MASTER_PW[] = "MASTER_PASSWORD = '%s';";
#if defined (SS_DEBUG)
string change_cmd_nopw = change_cmd;
change_cmd_nopw += string_printf(MASTER_PW, "******");
MXS_DEBUG("Change master command is '%s'.", change_cmd_nopw.c_str());
#endif
change_cmd += string_printf(MASTER_PW, op.replication_password.c_str());
return change_cmd;
}
string SlaveStatus::to_string() const string SlaveStatus::to_string() const
{ {
// Print all of this on the same line to make things compact. Are the widths reasonable? The format is // Print all of this on the same line to make things compact. Are the widths reasonable? The format is
@ -1601,6 +1866,20 @@ string SlaveStatus::to_string() const
return rval; return rval;
} }
string SlaveStatus::to_short_string(const string& owner) const
{
if (name.empty())
{
return string_printf("Slave connection from %s to [%s]:%i",
owner.c_str(), master_host.c_str(), master_port);
}
else
{
return string_printf("Slave connection '%s' from %s to [%s]:%i",
name.c_str(), owner.c_str(), master_host.c_str(), master_port);
}
}
json_t* SlaveStatus::to_json() const json_t* SlaveStatus::to_json() const
{ {
json_t* result = json_object(); json_t* result = json_object();

View File

@ -35,6 +35,8 @@ public:
SLAVE_IO_NO, SLAVE_IO_NO,
}; };
bool exists = true; /* Has this connection been removed from the
* server but the monitor hasn't updated yet? */
bool seen_connected = false; /* Has this slave connection been seen connected, bool seen_connected = false; /* Has this slave connection been seen connected,
* meaning that the master server id is correct? * meaning that the master server id is correct?
**/ **/
@ -60,6 +62,7 @@ public:
std::string to_string() const; std::string to_string() const;
json_t* to_json() const; json_t* to_json() const;
std::string to_short_string(const std::string& owner) const;
static slave_io_running_t slave_io_from_string(const std::string& str); static slave_io_running_t slave_io_from_string(const std::string& str);
static std::string slave_io_to_string(slave_io_running_t slave_io); static std::string slave_io_to_string(slave_io_running_t slave_io);
}; };
@ -497,7 +500,7 @@ public:
* @param op Cluster operation descriptor * @param op Cluster operation descriptor
* @return True if successful * @return True if successful
*/ */
bool promote_v2(ClusterOperation* operation); bool promote(ClusterOperation& operation);
private: private:
class EventInfo; class EventInfo;
@ -528,7 +531,7 @@ private:
bool alter_event(const EventInfo& event, const std::string& target_status, bool alter_event(const EventInfo& event, const std::string& target_status,
json_t** error_out); json_t** error_out);
bool stop_slave_conn(const std::string& conn_name, StopMode mode, maxbase::Duration time_limit, bool stop_slave_conn(SlaveStatus* slave_conn, StopMode mode, maxbase::Duration time_limit,
json_t** error_out); json_t** error_out);
bool execute_cmd_ex(const std::string& cmd, QueryRetryMode mode, bool execute_cmd_ex(const std::string& cmd, QueryRetryMode mode,
@ -537,7 +540,11 @@ private:
bool execute_cmd_time_limit(const std::string& cmd, maxbase::Duration time_limit, bool execute_cmd_time_limit(const std::string& cmd, maxbase::Duration time_limit,
std::string* errmsg_out); std::string* errmsg_out);
bool set_read_only(ReadOnlySetting value, maxbase::Duration time_limit, json_t** error_out); bool set_read_only(ReadOnlySetting value, maxbase::Duration time_limit, json_t** error_out);
bool copy_master_slave_conns(ClusterOperation& op);
bool create_start_slave(ClusterOperation& op, const SlaveStatus& slave_conn);
SlaveStatus* slave_connection_status_mutable(const MariaDBServer* target);
std::string generate_change_master_cmd(ClusterOperation& op, const SlaveStatus& slave_conn);
}; };
/** /**