Esa Korhonen 4fd4b726a1 MXS-2325 Only enable events that were enabled on the master
The monitor now continuously updates a list of enabled server events. When
promoting a new master in failover/switchover, only events that were enabled
on the previous master are enabled on the new. This avoids enabling events
that may have been disabled on the master yet stayed in the SLAVESIDE_DISABLED-
state on the slave.

In the case of reset-replication command, events on the new master are only
enabled if the monitor had a master when the command was launched. Otherwise
all events remain disabled.
2019-03-04 16:00:07 +02:00

506 lines
15 KiB
C++

/*
* Copyright (c) 2018 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2022-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "server_utils.hh"
#include <algorithm>
#include <inttypes.h>
#include <maxbase/assert.h>
#include <maxscale/utils.hh>
using std::string;
using maxscale::string_printf;
namespace
{
// Used for Slave_IO_Running
const char YES[] = "Yes";
const char PREPARING[] = "Preparing";
const char CONNECTING[] = "Connecting";
const char NO[] = "No";
}
string SlaveStatus::to_string() const
{
// Print all of this on the same line to make things compact. Are the widths reasonable? The format is
// not quite array-like since usually there is just one row. May be changed later.
// Form the components of the line.
string host_port = string_printf("[%s]:%d", master_host.c_str(), master_port);
string running_states = string_printf("%s/%s",
slave_io_to_string(slave_io_running).c_str(),
slave_sql_running ? "Yes" : "No");
string rval = string_printf(
" Host: %22s, IO/SQL running: %7s, Master ID: %4" PRId64 ", Gtid_IO_Pos: %s, R.Lag: %d",
host_port.c_str(),
running_states.c_str(),
master_server_id,
gtid_io_pos.to_string().c_str(),
seconds_behind_master);
return rval;
}
string SlaveStatus::to_short_string() const
{
if (name.empty())
{
return string_printf("Slave connection from %s to [%s]:%i",
owning_server.c_str(), master_host.c_str(), master_port);
}
else
{
return string_printf("Slave connection '%s' from %s to [%s]:%i",
name.c_str(), owning_server.c_str(), master_host.c_str(), master_port);
}
}
json_t* SlaveStatus::to_json() const
{
json_t* result = json_object();
json_object_set_new(result, "connection_name", json_string(name.c_str()));
json_object_set_new(result, "master_host", json_string(master_host.c_str()));
json_object_set_new(result, "master_port", json_integer(master_port));
json_object_set_new(result,
"slave_io_running",
json_string(slave_io_to_string(slave_io_running).c_str()));
json_object_set_new(result, "slave_sql_running", json_string(slave_sql_running ? "Yes" : "No"));
json_object_set_new(result,
"seconds_behing_master",
seconds_behind_master == MXS_RLAG_UNDEFINED ? json_null() :
json_integer(seconds_behind_master));
json_object_set_new(result, "master_server_id", json_integer(master_server_id));
json_object_set_new(result, "last_io_or_sql_error", json_string(last_error.c_str()));
json_object_set_new(result, "gtid_io_pos", json_string(gtid_io_pos.to_string().c_str()));
return result;
}
SlaveStatus::slave_io_running_t SlaveStatus::slave_io_from_string(const std::string& str)
{
slave_io_running_t rval = SLAVE_IO_NO;
if (str == YES)
{
rval = SLAVE_IO_YES;
}
// Interpret "Preparing" as "Connecting". It's not quite clear if the master server id has been read
// or if server versions between master and slave have been checked, so better be on the safe side.
else if (str == CONNECTING || str == PREPARING)
{
rval = SLAVE_IO_CONNECTING;
}
else if (str != NO)
{
MXS_ERROR("Unexpected value for Slave_IO_Running: '%s'.", str.c_str());
}
return rval;
}
string SlaveStatus::slave_io_to_string(SlaveStatus::slave_io_running_t slave_io)
{
string rval;
switch (slave_io)
{
case SlaveStatus::SLAVE_IO_YES:
rval = YES;
break;
case SlaveStatus::SLAVE_IO_CONNECTING:
rval = CONNECTING;
break;
case SlaveStatus::SLAVE_IO_NO:
rval = NO;
break;
default:
mxb_assert(!false);
}
return rval;
}
bool SlaveStatus::should_be_copied(string* ignore_reason_out) const
{
bool accepted = true;
auto master_id = 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_sql_running)
{
accepted = false;
ignore_reason = "its slave sql thread is not running.";
}
else if (!seen_connected)
{
accepted = false;
ignore_reason = "it has not been seen connected to master.";
}
else if (master_id <= 0)
{
accepted = false;
ignore_reason = string_printf("its Master_Server_Id (%" PRIi64 ") is invalid .", master_id);
}
if (!accepted)
{
*ignore_reason_out = ignore_reason;
}
return accepted;
}
ServerOperation::ServerOperation(MariaDBServer* target, bool was_is_master, bool handle_events,
const std::string& sql_file, const SlaveStatusArray& conns_to_copy,
const EventNameSet& events_to_enable)
: target(target)
, to_from_master(was_is_master)
, handle_events(handle_events)
, sql_file(sql_file)
, conns_to_copy(conns_to_copy)
, events_to_enable(events_to_enable)
{
}
ServerOperation::ServerOperation(MariaDBServer* target, bool was_is_master, bool handle_events,
const std::string& sql_file)
: ServerOperation(target, was_is_master, handle_events, sql_file, {}, {})
{
}
GeneralOpData::GeneralOpData(const string& replication_user, const string& replication_password,
json_t** error, maxbase::Duration time_remaining)
: replication_user(replication_user)
, replication_password(replication_password)
, error_out(error)
, time_remaining(time_remaining)
{
}
GtidList GtidList::from_string(const string& gtid_string)
{
mxb_assert(gtid_string.size());
GtidList rval;
bool error = false;
bool have_more = false;
const char* str = gtid_string.c_str();
do
{
char* endptr = NULL;
auto new_triplet = Gtid::from_string(str, &endptr);
if (new_triplet.m_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(), Gtid::compare_domains);
}
return rval;
}
string GtidList::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 GtidList::can_replicate_from(const GtidList& 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(master_gtid, MISSING_DOMAIN_IGNORE) == 0;
}
bool GtidList::empty() const
{
return m_triplets.empty();
}
bool GtidList::operator==(const GtidList& rhs) const
{
return m_triplets == rhs.m_triplets;
}
uint64_t GtidList::events_ahead(const GtidList& rhs, substraction_mode_t domain_substraction_mode) const
{
const size_t n_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;
// GtidLists are assumed to be ordered by domain in ascending order.
while (ind_lhs < n_lhs && ind_rhs < n_rhs)
{
auto lhs_triplet = m_triplets[ind_lhs];
auto rhs_triplet = rhs.m_triplets[ind_rhs];
// Server id -1 should never be saved in a real gtid variable.
mxb_assert(lhs_triplet.m_server_id != SERVER_ID_UNKNOWN
&& rhs_triplet.m_server_id != SERVER_ID_UNKNOWN);
// Search for matching domain_id:s, advance the smaller one.
if (lhs_triplet.m_domain < rhs_triplet.m_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.m_sequence;
}
ind_lhs++;
}
else if (lhs_triplet.m_domain > rhs_triplet.m_domain)
{
ind_rhs++;
}
else
{
// Domains match, check sequences.
if (lhs_triplet.m_sequence > rhs_triplet.m_sequence)
{
/* Same domains, but lhs sequence is ahead of rhs sequence. */
events += lhs_triplet.m_sequence - rhs_triplet.m_sequence;
}
// Continue to next domains.
ind_lhs++;
ind_rhs++;
}
}
// If LHS has domains with higher id:s than at RHS, those domains need to be iterated here.
// This only affects the result if the LHS_ADD-mode is used.
if (domain_substraction_mode == MISSING_DOMAIN_LHS_ADD)
{
for (; ind_lhs < n_lhs; ind_lhs++)
{
events += m_triplets[ind_lhs].m_sequence;
}
}
return events;
}
Gtid Gtid::from_string(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. */
mxb_assert(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);
// Check for parse error. Even this is not quite enough because strtoull will silently convert
// negative values. Yet, strtoull is required for the third value.
if (errno != 0 || strtoull_endptr == ptr)
{
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 Gtid((uint32_t)parsed_numbers[0], parsed_numbers[1], parsed_numbers[2]);
}
else
{
return Gtid();
}
}
Gtid::Gtid()
: m_domain(0)
, m_server_id(SERVER_ID_UNKNOWN)
, m_sequence(0)
{
}
Gtid::Gtid(uint32_t domain, int64_t server_id, uint64_t sequence)
: m_domain(domain)
, m_server_id(server_id)
, m_sequence(sequence)
{
}
bool Gtid::eq(const Gtid& rhs) const
{
return m_domain == rhs.m_domain && m_server_id == rhs.m_server_id && m_sequence == rhs.m_sequence;
}
string Gtid::to_string() const
{
string rval;
if (m_server_id != SERVER_ID_UNKNOWN)
{
rval += string_printf("%" PRIu32 "-%" PRIi64 "-%" PRIu64, m_domain, m_server_id, m_sequence);
}
return rval;
}
Gtid GtidList::get_gtid(uint32_t domain) const
{
Gtid rval;
// Make a dummy triplet for the domain search
Gtid search_val(domain, -1, 0);
auto found = std::lower_bound(m_triplets.begin(),
m_triplets.end(),
search_val,
Gtid::compare_domains);
if (found != m_triplets.end() && found->m_domain == domain)
{
rval = *found;
}
return rval;
}
QueryResult::QueryResult(MYSQL_RES* resultset)
: m_resultset(resultset)
{
if (m_resultset)
{
auto columns = mysql_num_fields(m_resultset);
MYSQL_FIELD* field_info = mysql_fetch_fields(m_resultset);
for (int64_t column_index = 0; column_index < columns; column_index++)
{
string key(field_info[column_index].name);
// TODO: Think of a way to handle duplicate names nicely. Currently this should only be used
// for known queries.
mxb_assert(m_col_indexes.count(key) == 0);
m_col_indexes[key] = column_index;
}
}
}
QueryResult::~QueryResult()
{
if (m_resultset)
{
mysql_free_result(m_resultset);
}
}
bool QueryResult::next_row()
{
mxb_assert(m_resultset);
m_rowdata = mysql_fetch_row(m_resultset);
if (m_rowdata)
{
m_current_row_ind++;
return true;
}
return false;
}
int64_t QueryResult::get_current_row_index() const
{
return m_current_row_ind;
}
int64_t QueryResult::get_col_count() const
{
return m_resultset ? mysql_num_fields(m_resultset) : -1;
}
int64_t QueryResult::get_row_count() const
{
return m_resultset ? mysql_num_rows(m_resultset) : -1;
}
int64_t QueryResult::get_col_index(const string& col_name) const
{
auto iter = m_col_indexes.find(col_name);
return (iter != m_col_indexes.end()) ? iter->second : -1;
}
string QueryResult::get_string(int64_t column_ind) const
{
mxb_assert(column_ind < get_col_count() && column_ind >= 0);
char* data = m_rowdata[column_ind];
return data ? data : "";
}
int64_t QueryResult::get_uint(int64_t column_ind) const
{
mxb_assert(column_ind < get_col_count() && column_ind >= 0);
char* data = m_rowdata[column_ind];
int64_t rval = -1;
if (data && *data)
{
errno = 0; // strtoll sets this
char* endptr = NULL;
auto parsed = strtoll(data, &endptr, 10);
if (parsed >= 0 && errno == 0 && *endptr == '\0')
{
rval = parsed;
}
}
return rval;
}
bool QueryResult::get_bool(int64_t column_ind) const
{
mxb_assert(column_ind < get_col_count() && column_ind >= 0);
char* data = m_rowdata[column_ind];
return data ? (strcmp(data, "Y") == 0 || strcmp(data, "1") == 0) : false;
}