If MAXSCALE_SCHEMA.REPLICATION_HEARTBEAT isn't replicated, a warning is logged.
This commit is contained in:
Markus Makela
2015-10-27 20:25:12 +02:00
parent c1eb84b377
commit cd55f62051
3 changed files with 365 additions and 1 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
}