diff --git a/server/modules/monitor/mariadbmon/cluster_manipulation.cc b/server/modules/monitor/mariadbmon/cluster_manipulation.cc index c0fa1069b..6ca0d80a5 100644 --- a/server/modules/monitor/mariadbmon/cluster_manipulation.cc +++ b/server/modules/monitor/mariadbmon/cluster_manipulation.cc @@ -811,7 +811,7 @@ bool MariaDBMonitor::failover_wait_relay_log(MXS_MONITORED_SERVER* new_master, i new_master->server->unique_name, master_info->relay_log_events()); thread_millisleep(1000); // Sleep for a while before querying server again. // Todo: check server version before entering failover. - Gtid old_gtid_io_pos = master_info->slave_status.gtid_io_pos; + GtidTriplet old_gtid_io_pos = master_info->slave_status.gtid_io_pos; // Update gtid:s first to make sure Gtid_IO_Pos is the more recent value. // It doesn't matter here, but is a general rule. query_ok = master_info->update_gtids(m_master_gtid_domain) && @@ -962,7 +962,7 @@ bool MariaDBMonitor::switchover_demote_master(MXS_MONITORED_SERVER* current_mast * @param err_out json object for error printing. Can be NULL. * @return True, if target gtid was reached within allotted time for all servers */ -bool MariaDBMonitor::switchover_wait_slaves_catchup(const ServerVector& slaves, const Gtid& gtid, +bool MariaDBMonitor::switchover_wait_slaves_catchup(const ServerVector& slaves, const GtidTriplet& gtid, int total_timeout, int read_timeout, json_t** err_out) { bool success = true; @@ -1003,7 +1003,7 @@ bool MariaDBMonitor::switchover_wait_slaves_catchup(const ServerVector& slaves, * @param err_out json object for error printing. Can be NULL. * @return True, if target gtid was reached within allotted time */ -bool MariaDBMonitor::switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const Gtid& gtid, +bool MariaDBMonitor::switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const GtidTriplet& gtid, int total_timeout, int read_timeout, json_t** err_out) { @@ -1076,7 +1076,7 @@ bool MariaDBMonitor::wait_cluster_stabilization(MXS_MONITORED_SERVER* new_master int query_fails = 0; int repl_fails = 0; int successes = 0; - const Gtid target = new_master_info->gtid_current_pos; + const GtidTriplet target = new_master_info->gtid_current_pos; ServerVector wait_list = slaves; // Check all the servers in the list bool first_round = true; bool time_is_up = false; @@ -1498,8 +1498,8 @@ bool MariaDBMonitor::can_replicate_from(MXS_MONITORED_SERVER* slave, bool rval = false; if (slave_info->update_gtids(m_master_gtid_domain)) { - Gtid slave_gtid = slave_info->gtid_current_pos; - Gtid master_gtid = master_info->gtid_binlog_pos; + GtidTriplet slave_gtid = slave_info->gtid_current_pos; + GtidTriplet master_gtid = master_info->gtid_binlog_pos; // The following are not sufficient requirements for replication to work, they only cover the basics. // If the servers have diverging histories, the redirection will seem to succeed but the slave IO // thread will stop in error. diff --git a/server/modules/monitor/mariadbmon/mariadbmon.hh b/server/modules/monitor/mariadbmon/mariadbmon.hh index 985311ae4..226f175ef 100644 --- a/server/modules/monitor/mariadbmon/mariadbmon.hh +++ b/server/modules/monitor/mariadbmon/mariadbmon.hh @@ -197,9 +197,9 @@ private: bool failover_wait_relay_log(MXS_MONITORED_SERVER* new_master, int seconds_remaining, json_t** err_out); bool switchover_demote_master(MXS_MONITORED_SERVER* current_master, MariaDBServer* info, json_t** err_out); - bool switchover_wait_slaves_catchup(const ServerVector& slaves, const Gtid& gtid, int total_timeout, + bool switchover_wait_slaves_catchup(const ServerVector& slaves, const GtidTriplet& gtid, int total_timeout, int read_timeout, json_t** err_out); - bool switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const Gtid& gtid, + bool switchover_wait_slave_catchup(MXS_MONITORED_SERVER* slave, const GtidTriplet& gtid, int total_timeout, int read_timeout, json_t** err_out); bool wait_cluster_stabilization(MXS_MONITORED_SERVER* new_master, const ServerVector& slaves, int seconds_remaining); diff --git a/server/modules/monitor/mariadbmon/mariadbserver.cc b/server/modules/monitor/mariadbmon/mariadbserver.cc index a560cb239..8c86a7c97 100644 --- a/server/modules/monitor/mariadbmon/mariadbserver.cc +++ b/server/modules/monitor/mariadbmon/mariadbserver.cc @@ -203,11 +203,12 @@ bool MariaDBServer::do_show_slave_status(int64_t gtid_domain) { string gtid_io_pos = result->get_string(i_gtid_io_pos); slave_status.gtid_io_pos = !gtid_io_pos.empty() ? - Gtid(gtid_io_pos.c_str(), gtid_domain) : Gtid(); + GtidTriplet(gtid_io_pos.c_str(), gtid_domain) : + GtidTriplet(); } else { - slave_status.gtid_io_pos = Gtid(); + slave_status.gtid_io_pos = GtidTriplet(); } } } diff --git a/server/modules/monitor/mariadbmon/mariadbserver.hh b/server/modules/monitor/mariadbmon/mariadbserver.hh index b3fdfe16d..b355cc5fd 100644 --- a/server/modules/monitor/mariadbmon/mariadbserver.hh +++ b/server/modules/monitor/mariadbmon/mariadbserver.hh @@ -47,7 +47,7 @@ public: * reading from. */ uint64_t read_master_log_pos; /**< Position up to which the I/O thread has read in the current master * binary log file. */ - Gtid gtid_io_pos; /**< Gtid I/O position of the slave thread. Only shows the triplet with + GtidTriplet gtid_io_pos; /**< Gtid I/O position of the slave thread. Only shows the triplet with * the current master domain. */ std::string last_error; /**< Last IO or SQL error encountered. */ @@ -92,9 +92,9 @@ public: time_t latest_event; /**< Time when latest event was received from the master */ int64_t gtid_domain_id; /**< The value of gtid_domain_id, the domain which is used for * new non-replicated events. */ - Gtid gtid_current_pos; /**< Gtid of latest event. Only shows the triplet + GtidTriplet gtid_current_pos; /**< Gtid of latest event. Only shows the triplet * with the current master domain. */ - Gtid gtid_binlog_pos; /**< Gtid of latest event written to binlog. Only shows + GtidTriplet gtid_binlog_pos; /**< Gtid of latest event written to binlog. Only shows * the triplet with the current master domain. */ SlaveStatusInfo slave_status; /**< Data returned from SHOW SLAVE STATUS */ ReplicationSettings rpl_settings; /**< Miscellaneous replication related settings */ diff --git a/server/modules/monitor/mariadbmon/utilities.cc b/server/modules/monitor/mariadbmon/utilities.cc index 63f5de8b9..7dc9be706 100644 --- a/server/modules/monitor/mariadbmon/utilities.cc +++ b/server/modules/monitor/mariadbmon/utilities.cc @@ -13,6 +13,7 @@ #include "utilities.hh" +#include #include #include #include @@ -201,25 +202,204 @@ bool QueryResult::get_bool(int64_t column_ind) const return data ? (strcmp(data,"Y") == 0 || strcmp(data, "1") == 0) : false; } -Gtid QueryResult::get_gtid(int64_t column_ind, int64_t gtid_domain) const +GtidTriplet QueryResult::get_gtid(int64_t column_ind, int64_t gtid_domain) const { ss_dassert(column_ind < m_columns); char* data = m_rowdata[column_ind]; - Gtid rval; + GtidTriplet rval; if (data && *data) { - rval = Gtid(data, gtid_domain); + rval = GtidTriplet(data, gtid_domain); } return rval; } -Gtid::Gtid() +Gtid Gtid::from_string(const std::string& gtid_string) +{ + ss_dassert(gtid_string.size()); + Gtid rval; + bool error = false; + bool have_more = false; + const char* str = gtid_string.c_str(); + do + { + char* endptr = NULL; + auto new_triplet = GtidTriplet::parse_one_triplet(str, &endptr); + if (new_triplet.server_id == SERVER_ID_UNKNOWN) + { + error = true; + } + else + { + rval.m_triplets.push_back(new_triplet); + // The last number must be followed by ',' (another triplet) or \0 (last triplet) + if (*endptr == ',') + { + have_more = true; + str = endptr + 1; + } + else if (*endptr == '\0') + { + have_more = false; + } + else + { + error = true; + } + } + } while (have_more && !error); + + if (error) + { + // If error occurred, clear the gtid as something is very wrong. + rval.m_triplets.clear(); + } + else + { + // Usually the servers gives the triplets ordered by domain id:s, but this is not 100%. + std::sort(rval.m_triplets.begin(), rval.m_triplets.end(), GtidTriplet::compare_domains); + } + return rval; +} + +string Gtid::to_string() const +{ + string rval; + string separator; + for (auto iter = m_triplets.begin(); iter != m_triplets.end(); iter++) + { + rval += separator + iter->to_string(); + separator = ","; + } + return rval; +} + +bool Gtid::can_replicate_from(const Gtid& master_gtid) +{ + /* The result of this function is false if the source and master have a common domain id where + * the source is ahead of the master. */ + return (events_ahead(*this, master_gtid, MISSING_DOMAIN_IGNORE) == 0); +} + +bool Gtid::empty() const +{ + return m_triplets.empty(); +} + +bool Gtid::operator == (const Gtid& rhs) const +{ + return m_triplets == rhs.m_triplets; +} + +uint64_t Gtid::events_ahead(const Gtid& lhs, const Gtid& rhs, substraction_mode_t domain_substraction_mode) +{ + const size_t n_lhs = lhs.m_triplets.size(); + const size_t n_rhs = rhs.m_triplets.size(); + size_t ind_lhs = 0, ind_rhs = 0; + uint64_t events = 0; + + while (ind_lhs < n_lhs && ind_rhs < n_rhs) + { + auto lhs_triplet = lhs.m_triplets[ind_lhs]; + auto rhs_triplet = rhs.m_triplets[ind_rhs]; + // Server id -1 should never be saved in a real gtid variable. + ss_dassert(lhs_triplet.server_id != SERVER_ID_UNKNOWN && + rhs_triplet.server_id != SERVER_ID_UNKNOWN); + // Search for matching domain_id:s, advance the smaller one. + if (lhs_triplet.domain < rhs_triplet.domain) + { + if (domain_substraction_mode == MISSING_DOMAIN_LHS_ADD) + { + // The domain on lhs does not exist on rhs. Add entire sequence number of lhs to the result. + events += lhs_triplet.sequence; + } + ind_lhs++; + } + else if (lhs_triplet.domain > rhs_triplet.domain) + { + ind_rhs++; + } + else + { + // Domains match, check sequences. + if (lhs_triplet.sequence > rhs_triplet.sequence) + { + /* Same domains, but lhs sequence is equal or ahead of rhs sequence. */ + events += lhs_triplet.sequence - rhs_triplet.sequence; + } + // Continue to next domains. + ind_lhs++; + ind_rhs++; + } + } + return events; +} + +GtidTriplet GtidTriplet::parse_one_triplet(const char* str, char** endptr) +{ + /* Error checking the gtid string is a bit questionable, as having an error means that the server is + buggy or network has faults, in which case nothing can be trusted. But without error checking + MaxScale may crash if string is wrong. */ + ss_dassert(endptr); + const char* ptr = str; + char* strtoull_endptr = NULL; + // Parse three numbers separated by - + uint64_t parsed_numbers[3]; + bool error = false; + for (int i = 0; i < 3 && !error; i++) + { + errno = 0; + parsed_numbers[i] = strtoull(ptr, &strtoull_endptr, 10); + // No parse error + if (errno != 0) + { + error = true; + } + else if (i < 2) + { + // First two numbers must be followed by a - + if (*strtoull_endptr == '-') + { + ptr = strtoull_endptr + 1; + } + else + { + error = true; + } + } + } + + // Check that none of the parsed numbers are unexpectedly large. This shouldn't really be possible unless + // server has a bug or network had an error. + if (!error && (parsed_numbers[0] > UINT32_MAX || parsed_numbers[1] > UINT32_MAX)) + { + error = true; + } + + if (!error) + { + *endptr = strtoull_endptr; + return GtidTriplet((uint32_t)parsed_numbers[0], parsed_numbers[1], parsed_numbers[2]); + } + else + { + return GtidTriplet(); + } +} + +GtidTriplet::GtidTriplet() : domain(0) , server_id(SERVER_ID_UNKNOWN) , sequence(0) {} -Gtid::Gtid(const char* str, int64_t search_domain) +GtidTriplet::GtidTriplet(uint32_t _domain, int64_t _server_id, uint64_t _sequence) + : domain(_domain) + , server_id(_server_id) + , sequence(_sequence) +{} + +GtidTriplet::GtidTriplet(const char* str, int64_t search_domain) : domain(0) , server_id(SERVER_ID_UNKNOWN) , sequence(0) @@ -245,14 +425,12 @@ Gtid::Gtid(const char* str, int64_t search_domain) } } -bool Gtid::operator == (const Gtid& rhs) const +bool GtidTriplet::eq(const GtidTriplet& rhs) const { - return domain == rhs.domain && - server_id != SERVER_ID_UNKNOWN && server_id == rhs.server_id && - sequence == rhs.sequence; + return domain == rhs.domain && server_id == rhs.server_id && sequence == rhs.sequence; } -string Gtid::to_string() const +string GtidTriplet::to_string() const { std::stringstream ss; if (server_id != SERVER_ID_UNKNOWN) @@ -262,13 +440,13 @@ string Gtid::to_string() const return ss.str(); } -void Gtid::parse_triplet(const char* str) +void GtidTriplet::parse_triplet(const char* str) { ss_debug(int rv = ) sscanf(str, "%" PRIu32 "-%" PRId64 "-%" PRIu64, &domain, &server_id, &sequence); ss_dassert(rv == 3); } -string Gtid::generate_master_gtid_wait_cmd(double timeout) const +string GtidTriplet::generate_master_gtid_wait_cmd(double timeout) const { std::stringstream query_ss; query_ss << "SELECT MASTER_GTID_WAIT(\"" << to_string() << "\", " << timeout << ");"; diff --git a/server/modules/monitor/mariadbmon/utilities.hh b/server/modules/monitor/mariadbmon/utilities.hh index fddfabcfe..7be3c54f8 100644 --- a/server/modules/monitor/mariadbmon/utilities.hh +++ b/server/modules/monitor/mariadbmon/utilities.hh @@ -72,25 +72,40 @@ string get_connection_errors(const ServerVector& servers); */ string monitored_servers_to_string(const ServerVector& array); -class Gtid +/** + * Class which encapsulates a gtid triplet (one --) + */ +class GtidTriplet { public: uint32_t domain; int64_t server_id; // Is actually 32bit unsigned. 0 is only used by server versions <= 10.1 uint64_t sequence; - Gtid(); + + GtidTriplet(); + GtidTriplet(uint32_t _domain, int64_t _server_id, uint64_t _sequence); /** * Parse a Gtid-triplet from a string. In case of a multi-triplet value, only the triplet with - * the given domain is returned. + * the given domain is returned. TODO: Remove once no longer used * * @param str Gtid string * @param search_domain The Gtid domain whose triplet should be returned. Negative domain stands for * autoselect, which is only allowed when the string contains one triplet. */ - Gtid(const char* str, int64_t search_domain = -1); + GtidTriplet(const char* str, int64_t search_domain = -1); - bool operator == (const Gtid& rhs) const; + /** + * Parse one triplet from null-terminated string. Handles multi-domain gtid:s properly. Should be called + * repeatedly for a multi-domain gtid string by giving the value of @c endptr as @c str. + * + * @param str First number of a triplet in a gtid-string + * @param endptr A pointer to save the position at after the last parsed character. + * @return A new GtidTriplet. If an error occurs, the server_id of the returned triplet is -1. + */ + static GtidTriplet parse_one_triplet(const char* str, char** endptr); + + bool eq(const GtidTriplet& rhs) const; std::string to_string() const; @@ -102,10 +117,99 @@ public: */ std::string generate_master_gtid_wait_cmd(double timeout) const; + /** + * Comparator, used when sorting by domain id. + * + * @param triplet1 Left side + * @param triplet2 Right side + * @return True if left < right + */ + static bool compare_domains(const GtidTriplet& triplet1, const GtidTriplet& triplet2) + { + return triplet1.domain < triplet2.domain; + } + private: void parse_triplet(const char* str); }; +inline bool operator == (const GtidTriplet& lhs, const GtidTriplet& rhs) +{ + return lhs.eq(rhs); +} + +class Gtid +{ +public: + enum substraction_mode_t + { + MISSING_DOMAIN_IGNORE, + MISSING_DOMAIN_LHS_ADD + }; + + /** + * Parse the gtid string and return an object. Orders the triplets by domain id. + * + * @param gtid_string gtid as given by server. String must not be empty. + * @return The parsed (possibly multidomain) gtid. In case of error, the gtid will be empty. + */ + static Gtid from_string(const std::string& gtid_string); + + /** + * Return a string version of the gtid. + * + * @return A string similar in form to how the server displays it + */ + std::string to_string() const; + + /** + * Check if a server with this gtid can replicate from a master with a given gtid. Only considers + * gtid:s and only detects obvious errors. The non-detected errors will mostly be detected once + * the slave tries to start replicating. + * + * TODO: Add support for Replicate_Do/Ignore_Id:s + * + * @param master_gtid Master server gtid + * @return True if replication looks possible + */ + bool can_replicate_from(const Gtid& master_gtid); + + /** + * Is the gtid empty. + * + * @return True if gtid has 0 triplets + */ + bool empty() const; + + /** + * Full comparison. + * + * @param rhs Other gtid + * @return True if both gtid:s have identical triplets or both are empty + */ + bool operator == (const Gtid& rhs) const; + + /** + * Calculate the number of events between two gtid:s with possibly multiple triplets. The + * result is always 0 or greater: if a sequence number of a domain on rhs is greater than on the same + * domain on lhs, the sequences are considered identical. Missing domains are handled depending on the + * value of @c domain_substraction_mode. + * + * @param lhs The value substracted from + * @param io_pos The value doing the substracting + * @param domain_substraction_mode How domains that exist on one side but not the other are handled. If + * MISSING_DOMAIN_IGNORE, these are simply ignored. If MISSING_DOMAIN_LHS_ADD, the sequence number on lhs + * is added to the total difference. + * @return The number of events between the two gtid:s + */ + static uint64_t events_ahead(const Gtid& lhs, const Gtid& rhs, + substraction_mode_t domain_substraction_mode); + +private: + std::vector m_triplets; +}; + + /** * Helper class for simplifying working with resultsets. Used in MariaDBServer. */ @@ -181,7 +285,7 @@ public: * @param gtid_domain Which gtid domain to parse * @return Value as a gtid. */ - Gtid get_gtid(int64_t column_ind, int64_t gtid_domain) const; + GtidTriplet get_gtid(int64_t column_ind, int64_t gtid_domain) const; private: MYSQL_RES* m_resultset; // Underlying result set, freed at dtor.