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 size_t log_ses_count[];
|
||||||
extern __thread log_info_t tls_log_info;
|
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(
|
static void modutil_reply_routing_error(
|
||||||
DCB* backend_dcb,
|
DCB* backend_dcb,
|
||||||
int error,
|
int error,
|
||||||
@ -842,3 +855,119 @@ int modutil_count_statements(GWBUF* buffer)
|
|||||||
|
|
||||||
return num;
|
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 <buffer.h>
|
||||||
#include <dcb.h>
|
#include <dcb.h>
|
||||||
#include <string.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_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)
|
#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);
|
const char *msg);
|
||||||
|
|
||||||
int modutil_count_signal_packets(GWBUF*,int,int,int*);
|
int modutil_count_signal_packets(GWBUF*,int,int,int*);
|
||||||
|
mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string);
|
||||||
#endif
|
#endif
|
||||||
|
@ -51,8 +51,9 @@
|
|||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <mysqlmon.h>
|
#include <mysqlmon.h>
|
||||||
|
#include <pcre2.h>
|
||||||
|
#include <modutil.h>
|
||||||
|
|
||||||
/** Defined in log_manager.cc */
|
/** Defined in log_manager.cc */
|
||||||
extern int lm_enabled_logfiles_bitmask;
|
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 void set_slave_heartbeat(MONITOR *, MONITOR_SERVERS *);
|
||||||
static int add_slave_to_master(long *, int, long);
|
static int add_slave_to_master(long *, int, long);
|
||||||
bool isMySQLEvent(monitor_event_t event);
|
bool isMySQLEvent(monitor_event_t event);
|
||||||
|
void check_maxscale_schema_replication(MONITOR *monitor);
|
||||||
static bool report_version_err = true;
|
static bool report_version_err = true;
|
||||||
|
static const char* hb_table_name = "maxscale_schema.replication_heartbeat";
|
||||||
|
|
||||||
static MONITOR_OBJECT MyObject = {
|
static MONITOR_OBJECT MyObject = {
|
||||||
startMonitor,
|
startMonitor,
|
||||||
stopMonitor,
|
stopMonitor,
|
||||||
@ -219,6 +223,7 @@ startMonitor(void *arg, void* opt)
|
|||||||
{
|
{
|
||||||
memset(handle->events,true,sizeof(handle->events));
|
memset(handle->events,true,sizeof(handle->events));
|
||||||
}
|
}
|
||||||
|
|
||||||
handle->tid = (THREAD)thread_start(monitorMain, monitor);
|
handle->tid = (THREAD)thread_start(monitorMain, monitor);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
@ -734,6 +739,7 @@ int num_servers=0;
|
|||||||
MONITOR_SERVERS *root_master = NULL;
|
MONITOR_SERVERS *root_master = NULL;
|
||||||
size_t nrounds = 0;
|
size_t nrounds = 0;
|
||||||
int log_no_master = 1;
|
int log_no_master = 1;
|
||||||
|
bool heartbeat_checked = false;
|
||||||
|
|
||||||
spinlock_acquire(&mon->lock);
|
spinlock_acquire(&mon->lock);
|
||||||
handle = (MYSQL_MONITOR *)mon->handle;
|
handle = (MYSQL_MONITOR *)mon->handle;
|
||||||
@ -762,6 +768,13 @@ detect_stale_master = handle->detectStaleMaster;
|
|||||||
}
|
}
|
||||||
/** Wait base interval */
|
/** Wait base interval */
|
||||||
thread_millisleep(MON_BASE_INTERVAL_MS);
|
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
|
* Calculate how far away the monitor interval is from its full
|
||||||
* cycle and if monitor interval time further than the base
|
* cycle and if monitor interval time further than the base
|
||||||
@ -1465,3 +1478,223 @@ bool isMySQLEvent(monitor_event_t event)
|
|||||||
}
|
}
|
||||||
return false;
|
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