Fix to MXS-29: https://mariadb.atlassian.net/browse/MXS-29
If MAXSCALE_SCHEMA.REPLICATION_HEARTBEAT isn't replicated, a warning is logged.
This commit is contained in:
@ -38,6 +38,19 @@ extern int lm_enabled_logfiles_bitmask;
|
||||
extern size_t log_ses_count[];
|
||||
extern __thread log_info_t tls_log_info;
|
||||
|
||||
/** These are used when converting MySQL wildcards to regular expressions */
|
||||
static SPINLOCK re_lock = SPINLOCK_INIT;
|
||||
static bool pattern_init = false;
|
||||
static pcre2_code *re_percent = NULL;
|
||||
static pcre2_code *re_single = NULL;
|
||||
static pcre2_code *re_escape = NULL;
|
||||
static const PCRE2_SPTR pattern_percent = (PCRE2_SPTR) "%";
|
||||
static const PCRE2_SPTR pattern_single = (PCRE2_SPTR) "([^\\\\]|^)_";
|
||||
static const PCRE2_SPTR pattern_escape = (PCRE2_SPTR) "[.]";
|
||||
static const char* sub_percent = ".*";
|
||||
static const char* sub_single = "$1.";
|
||||
static const char* sub_escape = "\\.";
|
||||
|
||||
static void modutil_reply_routing_error(
|
||||
DCB* backend_dcb,
|
||||
int error,
|
||||
@ -842,3 +855,119 @@ int modutil_count_statements(GWBUF* buffer)
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the PCRE2 patterns used when converting MySQL wildcards to PCRE syntax.
|
||||
*/
|
||||
void init_pcre2_patterns()
|
||||
{
|
||||
spinlock_acquire(&re_lock);
|
||||
if (!pattern_init)
|
||||
{
|
||||
int err;
|
||||
size_t erroff;
|
||||
PCRE2_UCHAR errbuf[STRERROR_BUFLEN];
|
||||
|
||||
if ((re_percent = pcre2_compile(pattern_percent, PCRE2_ZERO_TERMINATED,
|
||||
0, &err, &erroff, NULL)) &&
|
||||
(re_single = pcre2_compile(pattern_single, PCRE2_ZERO_TERMINATED,
|
||||
0, &err, &erroff, NULL)) &&
|
||||
(re_escape = pcre2_compile(pattern_escape, PCRE2_ZERO_TERMINATED,
|
||||
0, &err, &erroff, NULL)))
|
||||
{
|
||||
pattern_init = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pcre2_get_error_message(err, errbuf, sizeof(errbuf));
|
||||
skygw_log_write(LE, "Error: Failed to compile PCRE2 pattern: %s",
|
||||
errbuf);
|
||||
}
|
||||
|
||||
if (!pattern_init)
|
||||
{
|
||||
pcre2_code_free(re_percent);
|
||||
pcre2_code_free(re_single);
|
||||
pcre2_code_free(re_escape);
|
||||
re_percent = NULL;
|
||||
re_single = NULL;
|
||||
re_escape = NULL;
|
||||
}
|
||||
}
|
||||
spinlock_release(&re_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if @c string matches @c pattern according to the MySQL wildcard rules.
|
||||
* The wildcard character @c '%%' is replaced with @c '.*' and @c '_' is replaced
|
||||
* with @c '.'. All Regular expression special characters are escaped before
|
||||
* matching is made.
|
||||
* @param pattern Wildcard pattern
|
||||
* @param string String to match
|
||||
* @return MXS_PCRE2_MATCH if the pattern matches, MXS_PCRE2_NOMATCH if it does
|
||||
* not match and MXS_PCRE2_ERROR if an error occurred
|
||||
* @see maxscale_pcre2.h
|
||||
*/
|
||||
mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string)
|
||||
{
|
||||
if (!pattern_init)
|
||||
{
|
||||
init_pcre2_patterns();
|
||||
}
|
||||
mxs_pcre2_result_t rval = MXS_PCRE2_ERROR;
|
||||
bool err = false;
|
||||
PCRE2_SIZE matchsize = strlen(string) + 1;
|
||||
PCRE2_SIZE tempsize = matchsize;
|
||||
char* matchstr = (char*) malloc(matchsize);
|
||||
char* tempstr = (char*) malloc(tempsize);
|
||||
|
||||
pcre2_match_data *mdata_percent = pcre2_match_data_create_from_pattern(re_percent, NULL);
|
||||
pcre2_match_data *mdata_single = pcre2_match_data_create_from_pattern(re_single, NULL);
|
||||
pcre2_match_data *mdata_escape = pcre2_match_data_create_from_pattern(re_escape, NULL);
|
||||
|
||||
if (matchstr && tempstr && mdata_percent && mdata_single && mdata_escape)
|
||||
{
|
||||
if (mxs_pcre2_substitute(re_escape, pattern, sub_escape,
|
||||
&matchstr, &matchsize) == MXS_PCRE2_ERROR ||
|
||||
mxs_pcre2_substitute(re_single, matchstr, sub_single,
|
||||
&tempstr, &tempsize) == MXS_PCRE2_ERROR ||
|
||||
mxs_pcre2_substitute(re_percent, tempstr, sub_percent,
|
||||
&matchstr, &matchsize) == MXS_PCRE2_ERROR)
|
||||
{
|
||||
err = true;
|
||||
}
|
||||
|
||||
if (!err)
|
||||
{
|
||||
int errcode;
|
||||
rval = mxs_pcre2_simple_match(matchstr, string, PCRE2_CASELESS, &errcode);
|
||||
if (rval == MXS_PCRE2_ERROR)
|
||||
{
|
||||
if(errcode != 0)
|
||||
{
|
||||
PCRE2_UCHAR errbuf[STRERROR_BUFLEN];
|
||||
pcre2_get_error_message(errcode, errbuf, sizeof(errbuf));
|
||||
skygw_log_write(LE, "Error: Failed to match pattern: %s",
|
||||
errbuf);
|
||||
}
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
err = true;
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
skygw_log_write(LE, "Error: Fatal error when matching wildcard patterns.");
|
||||
}
|
||||
|
||||
pcre2_match_data_free(mdata_percent);
|
||||
pcre2_match_data_free(mdata_single);
|
||||
pcre2_match_data_free(mdata_escape);
|
||||
free(matchstr);
|
||||
free(tempstr);
|
||||
return rval;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <buffer.h>
|
||||
#include <dcb.h>
|
||||
#include <string.h>
|
||||
#include <maxscale_pcre2.h>
|
||||
|
||||
#define PTR_IS_RESULTSET(b) (b[0] == 0x01 && b[1] == 0x0 && b[2] == 0x0 && b[3] == 0x01)
|
||||
#define PTR_IS_EOF(b) (b[0] == 0x05 && b[1] == 0x0 && b[2] == 0x0 && b[4] == 0xfe)
|
||||
@ -68,4 +69,5 @@ GWBUF *modutil_create_mysql_err_msg(
|
||||
const char *msg);
|
||||
|
||||
int modutil_count_signal_packets(GWBUF*,int,int,int*);
|
||||
mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string);
|
||||
#endif
|
||||
|
@ -51,8 +51,9 @@
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
|
||||
#include <mysqlmon.h>
|
||||
#include <pcre2.h>
|
||||
#include <modutil.h>
|
||||
|
||||
/** Defined in log_manager.cc */
|
||||
extern int lm_enabled_logfiles_bitmask;
|
||||
@ -81,7 +82,10 @@ static void set_master_heartbeat(MYSQL_MONITOR *, MONITOR_SERVERS *);
|
||||
static void set_slave_heartbeat(MONITOR *, MONITOR_SERVERS *);
|
||||
static int add_slave_to_master(long *, int, long);
|
||||
bool isMySQLEvent(monitor_event_t event);
|
||||
void check_maxscale_schema_replication(MONITOR *monitor);
|
||||
static bool report_version_err = true;
|
||||
static const char* hb_table_name = "maxscale_schema.replication_heartbeat";
|
||||
|
||||
static MONITOR_OBJECT MyObject = {
|
||||
startMonitor,
|
||||
stopMonitor,
|
||||
@ -219,6 +223,7 @@ startMonitor(void *arg, void* opt)
|
||||
{
|
||||
memset(handle->events,true,sizeof(handle->events));
|
||||
}
|
||||
|
||||
handle->tid = (THREAD)thread_start(monitorMain, monitor);
|
||||
return handle;
|
||||
}
|
||||
@ -734,6 +739,7 @@ int num_servers=0;
|
||||
MONITOR_SERVERS *root_master = NULL;
|
||||
size_t nrounds = 0;
|
||||
int log_no_master = 1;
|
||||
bool heartbeat_checked = false;
|
||||
|
||||
spinlock_acquire(&mon->lock);
|
||||
handle = (MYSQL_MONITOR *)mon->handle;
|
||||
@ -762,6 +768,13 @@ detect_stale_master = handle->detectStaleMaster;
|
||||
}
|
||||
/** Wait base interval */
|
||||
thread_millisleep(MON_BASE_INTERVAL_MS);
|
||||
|
||||
if (handle->replicationHeartbeat && !heartbeat_checked)
|
||||
{
|
||||
check_maxscale_schema_replication(mon);
|
||||
heartbeat_checked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate how far away the monitor interval is from its full
|
||||
* cycle and if monitor interval time further than the base
|
||||
@ -1465,3 +1478,223 @@ bool isMySQLEvent(monitor_event_t event)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if replicate_ignore_table is defined and if maxscale_schema.replication_hearbeat
|
||||
* table is in the list.
|
||||
* @param database Server to check
|
||||
* @return False if the table is not replicated or an error occurred when querying
|
||||
* the server
|
||||
*/
|
||||
bool check_replicate_ignore_table(MONITOR_SERVERS* database)
|
||||
{
|
||||
MYSQL_RES *result;
|
||||
bool rval = true;
|
||||
|
||||
if (mysql_query(database->con,
|
||||
"show variables like 'replicate_ignore_table'") == 0 &&
|
||||
(result = mysql_store_result(database->con)) &&
|
||||
mysql_num_fields(result) > 1)
|
||||
{
|
||||
MYSQL_ROW row;
|
||||
|
||||
while ((row = mysql_fetch_row(result)))
|
||||
{
|
||||
if (strlen(row[1]) > 0 &&
|
||||
strcasestr(row[1], hb_table_name))
|
||||
{
|
||||
skygw_log_write(LE, "Warning: 'replicate_ignore_table' is "
|
||||
"defined on server '%s' and '%s' was found in it. ",
|
||||
database->server->unique_name, hb_table_name);
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
mysql_free_result(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
skygw_log_write(LE, "Error: Failed to query server %s for "
|
||||
"'replicate_ignore_table': %s",
|
||||
database->server->unique_name,
|
||||
mysql_error(database->con));
|
||||
rval = false;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if replicate_do_table is defined and if maxscale_schema.replication_hearbeat
|
||||
* table is not in the list.
|
||||
* @param database Server to check
|
||||
* @return False if the table is not replicated or an error occurred when querying
|
||||
* the server
|
||||
*/
|
||||
bool check_replicate_do_table(MONITOR_SERVERS* database)
|
||||
{
|
||||
MYSQL_RES *result;
|
||||
bool rval = true;
|
||||
|
||||
if (mysql_query(database->con,
|
||||
"show variables like 'replicate_do_table'") == 0 &&
|
||||
(result = mysql_store_result(database->con)) &&
|
||||
mysql_num_fields(result) > 1)
|
||||
{
|
||||
MYSQL_ROW row;
|
||||
|
||||
while ((row = mysql_fetch_row(result)))
|
||||
{
|
||||
if (strlen(row[1]) > 0 &&
|
||||
strcasestr(row[1], hb_table_name) == NULL)
|
||||
{
|
||||
skygw_log_write(LE, "Warning: 'replicate_do_table' is "
|
||||
"defined on server '%s' and '%s' was not found in it. ",
|
||||
database->server->unique_name, hb_table_name);
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
mysql_free_result(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
skygw_log_write(LE, "Error: Failed to query server %s for "
|
||||
"'replicate_do_table': %s",
|
||||
database->server->unique_name,
|
||||
mysql_error(database->con));
|
||||
rval = false;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if replicate_wild_do_table is defined and if it doesn't match
|
||||
* maxscale_schema.replication_heartbeat.
|
||||
* @param database Database server
|
||||
* @return False if the table is not replicated or an error occurred when trying to
|
||||
* query the server.
|
||||
*/
|
||||
bool check_replicate_wild_do_table(MONITOR_SERVERS* database)
|
||||
{
|
||||
MYSQL_RES *result;
|
||||
bool rval = true;
|
||||
|
||||
if (mysql_query(database->con,
|
||||
"show variables like 'replicate_wild_do_table'") == 0 &&
|
||||
(result = mysql_store_result(database->con)) &&
|
||||
mysql_num_fields(result) > 1)
|
||||
{
|
||||
MYSQL_ROW row;
|
||||
|
||||
while ((row = mysql_fetch_row(result)))
|
||||
{
|
||||
if (strlen(row[1]) > 0)
|
||||
{
|
||||
mxs_pcre2_result_t rc = modutil_mysql_wildcard_match(row[1], hb_table_name);
|
||||
if (rc == MXS_PCRE2_NOMATCH)
|
||||
{
|
||||
skygw_log_write(LE, "Warning: 'replicate_wild_do_table' is "
|
||||
"defined on server '%s' and '%s' does not match it. ",
|
||||
database->server->unique_name,
|
||||
hb_table_name);
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
mysql_free_result(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
skygw_log_write(LE, "Error: Failed to query server %s for "
|
||||
"'replicate_wild_do_table': %s",
|
||||
database->server->unique_name,
|
||||
mysql_error(database->con));
|
||||
rval = false;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if replicate_wild_ignore_table is defined and if it matches
|
||||
* maxscale_schema.replication_heartbeat.
|
||||
* @param database Database server
|
||||
* @return False if the table is not replicated or an error occurred when trying to
|
||||
* query the server.
|
||||
*/
|
||||
bool check_replicate_wild_ignore_table(MONITOR_SERVERS* database)
|
||||
{
|
||||
MYSQL_RES *result;
|
||||
bool rval = true;
|
||||
|
||||
if (mysql_query(database->con,
|
||||
"show variables like 'replicate_wild_ignore_table'") == 0 &&
|
||||
(result = mysql_store_result(database->con)) &&
|
||||
mysql_num_fields(result) > 1)
|
||||
{
|
||||
MYSQL_ROW row;
|
||||
|
||||
while ((row = mysql_fetch_row(result)))
|
||||
{
|
||||
if (strlen(row[1]) > 0)
|
||||
{
|
||||
mxs_pcre2_result_t rc = modutil_mysql_wildcard_match(row[1], hb_table_name);
|
||||
if (rc == MXS_PCRE2_MATCH)
|
||||
{
|
||||
skygw_log_write(LE, "Warning: 'replicate_wild_ignore_table' is "
|
||||
"defined on server '%s' and '%s' matches it. ",
|
||||
database->server->unique_name,
|
||||
hb_table_name);
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
mysql_free_result(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
skygw_log_write(LE, "Error: Failed to query server %s for "
|
||||
"'replicate_wild_do_table': %s",
|
||||
database->server->unique_name,
|
||||
mysql_error(database->con));
|
||||
rval = false;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the maxscale_schema.replication_heartbeat table is replicated on all
|
||||
* servers and log a warning if problems were found.
|
||||
* @param monitor Monitor structure
|
||||
*/
|
||||
void check_maxscale_schema_replication(MONITOR *monitor)
|
||||
{
|
||||
MONITOR_SERVERS* database = monitor->databases;
|
||||
bool err = false;
|
||||
|
||||
while (database)
|
||||
{
|
||||
connect_result_t rval = mon_connect_to_db(monitor, database);
|
||||
if (rval == MONITOR_CONN_OK)
|
||||
{
|
||||
if (!check_replicate_ignore_table(database) ||
|
||||
!check_replicate_do_table(database) ||
|
||||
!check_replicate_wild_do_table(database) ||
|
||||
!check_replicate_wild_ignore_table(database))
|
||||
{
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mon_log_connect_error(database, rval);
|
||||
}
|
||||
database = database->next;
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
skygw_log_write(LE, "Warning: Problems were encountered when "
|
||||
"checking if '%s' is replicated. Make sure that the table is "
|
||||
"replicated to all slaves.", hb_table_name);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user