Merge branch '2.2' into develop

This commit is contained in:
Johan Wikman 2018-02-08 12:48:06 +02:00
commit 5326c8db5c
41 changed files with 937 additions and 210 deletions

View File

@ -55,6 +55,7 @@ include_directories(BEFORE ${PCRE2_INCLUDE_DIRS})
if(NOT MARIADB_CONNECTOR_FOUND)
message(STATUS "Building MariaDB Connector-C from source.")
include(cmake/BuildMariaDBConnector.cmake)
include_directories(BEFORE ${MARIADB_CONNECTOR_INCLUDE_DIR})
else()
# This is required as the core depends on the `connector-c` target
add_custom_target(connector-c)

View File

@ -366,18 +366,19 @@ privilege.
parameters. If password encryption is in use, `replication_password` must be
encrypted with the same key to avoid erroneous decryption.
#### `failover_timeout`
#### `failover_timeout` and `switchover_timeout`
Time limit for the cluster failover in seconds. The default value is 90
seconds.
Time limit for the cluster failover and switchover in seconds. The default values
are 90 seconds.
If no successful failover takes place within the configured time period, a
message is logged and automatic failover is disabled.
If no successful failover/switchover takes place within the configured time
period, a message is logged and automatic failover is disabled. This prevents
further automatic modifications to the misbehaving cluster.
This parameter also controls how long a MaxScale instance that has transitioned
from passive to active will wait for a failover to take place after an apparent
loss of a master server. If no new master server is detected within the
configured time period, failover will be initiated again.
`failover_timeout` also controls how long a MaxScale instance that has
transitioned from passive to active will wait for a failover to take place after
an apparent loss of a master server. If no new master server is detected within
the configured time period, failover will be initiated again.
#### `verify_master_failure` and `master_failure_timeout`
@ -398,15 +399,17 @@ disconnection.
For automatic failover to activate, the `failcount` requirement must also be
met.
#### `switchover_timeout`
#### `servers_no_promotion`
Time limit for cluster switchover in seconds. The default value is 90
seconds.
This is a comma-separated list of server names that will not be chosen for
master promotion during a failover. This does not affect switchover since in
that case the user selects the server. Using this list can disrupt new master
selection such that an unoptimal server is chosen. At worst, this will cause
replication to break.
If no successful switchover takes place within the configured time period, a
message is logged and automatic failover is disabled, even if it was enabled
before the switchover attempt. This prevents further modifications to the
misbehaving cluster.
```
servers_no_promotion=backup_dc_server1,backup_dc_server2
```
### Manual switchover and failover

View File

@ -590,7 +590,8 @@ of parameters can be altered at runtime:
"auth_read_timeout",
"auth_write_timeout",
"admin_auth",
"admin_log_auth_failures"
"admin_log_auth_failures",
"passive"
]
## rotate

View File

@ -213,22 +213,25 @@ transaction_safety=on to enable detection of incomplete transactions.
### `send_slave_heartbeat`
This defines whether MariaDB MaxScale sends the heartbeat packet to the slave
when there are no real binlog events to send. The default value is 'off' and no
heartbeat events are sent to slave servers.
when there are no real binlog events to send. This parameter takes a boolean
value and the default value is false. This means that no heartbeat events are
sent to slave servers.
If value is 'on' the interval value (requested by the slave during registration)
is reported in the diagnostic output and the packet is send after the time
interval without any event to send.
If value is set to true the interval value (requested by the slave during
registration) is reported in the diagnostic output and the packet is send after
the time interval without any event to send.
### `semisync`
This parameter controls whether binlog server could ask Master server to start
the Semi-Synchronous replication. In order to get semi-sync working, the Master
server must have the *rpl_semi_sync_master* plugin installed. The availability
of the plugin and the value of the GLOBAL VARIABLE
*rpl_semi_sync_master_enabled* are checked in the Master registration phase: if
the plugin is installed in the Master database, the binlog server subsequently
requests the semi-sync option.
the Semi-Synchronous replication. This parameter takes a boolean value and the
default value is false.
In order to get semi-sync working, the Master server must have the
*rpl_semi_sync_master* plugin installed. The availability of the plugin and the
value of the GLOBAL VARIABLE *rpl_semi_sync_master_enabled* are checked in the
Master registration phase: if the plugin is installed in the Master database,
the binlog server subsequently requests the semi-sync option.
Note:
- the network replication stream from Master has two additional bytes before

View File

@ -214,7 +214,7 @@ typedef struct mxs_filter_object
* is changed these values must be updated in line with the rules in the
* file modinfo.h.
*/
#define MXS_FILTER_VERSION {2, 2, 0}
#define MXS_FILTER_VERSION {3, 0, 0}
/**
* MXS_FILTER_DEF represents a filter definition from the configuration file.

View File

@ -18,8 +18,8 @@
* @see http://jsonapi.org/format/
*/
#include <maxscale/cppdefs.hh>
#include <maxscale/jansson.hh>
#include <maxscale/cdefs.h>
#include <maxscale/jansson.h>
MXS_BEGIN_DECLS

View File

@ -18,13 +18,13 @@
#include <maxscale/cdefs.h>
#include <mysql.h>
#include <openssl/sha.h>
#include <maxscale/config.h>
#include <maxscale/dcb.h>
#include <maxscale/server.h>
#include <maxscale/jansson.h>
#include <maxscale/protocol/mysql.h>
MXS_BEGIN_DECLS
@ -95,7 +95,7 @@ typedef struct mxs_monitor_object
* The monitor API version number. Any change to the monitor module API
* must change these versions using the rules defined in modinfo.h
*/
#define MXS_MONITOR_VERSION {3, 0, 0}
#define MXS_MONITOR_VERSION {3, 1, 0}
/**
* Specifies capabilities specific for monitor.
@ -382,6 +382,19 @@ void load_server_journal(MXS_MONITOR *monitor, MXS_MONITORED_SERVER **master);
* @param search_server Server to search for
* @return Found monitored server or NULL if not found
*/
MXS_MONITORED_SERVER* mon_get_monitored_server(MXS_MONITOR* mon, SERVER* search_server);
MXS_MONITORED_SERVER* mon_get_monitored_server(const MXS_MONITOR* mon, SERVER* search_server);
/**
* Get an array of monitored servers. All the servers defined in the config setting must be monitored by
* the given monitor.
*
* @param params Config parameters
* @param key Setting name
* @param mon Monitor which should monitor the servers
* @param monitored_servers_out Where to save output. The caller should free the array, but not the elements.
* @return Output array size if successful, negative value otherwise
*/
int mon_config_get_servers(const MXS_CONFIG_PARAMETER* params, const char* key, const MXS_MONITOR* mon,
MXS_MONITORED_SERVER*** monitored_array_out);
MXS_END_DECLS

View File

@ -15,7 +15,7 @@
#include <maxscale/cdefs.h>
#include <stdlib.h>
#include <stdint.h>
#include <mysql.h>
#include <maxscale/protocol/mysql.h>
#include <maxscale/server.h>
MXS_BEGIN_DECLS
@ -124,4 +124,19 @@ mxs_mysql_name_kind_t mxs_mysql_name_to_pcre(char *pcre,
*/
void mxs_mysql_set_server_version(MYSQL* mysql, SERVER* server);
/**
* Enable/disable the logging of all SQL statements MaxScale sends to
* the servers.
*
* @param enable If true, enable, if false, disable.
*/
void mxs_mysql_set_log_statements(bool enable);
/**
* Returns whether SQL statements sent to the servers are logged or not.
*
* @return True, if statements are logged, false otherwise.
*/
bool mxs_mysql_get_log_statements();
MXS_END_DECLS

View File

@ -17,7 +17,7 @@
MXS_BEGIN_DECLS
#define QUERY_CLASSIFIER_VERSION {1, 1, 0}
#define MXS_QUERY_CLASSIFIER_VERSION {2, 0, 0}
/**
* qc_init_kind_t specifies what kind of initialization should be performed.

View File

@ -218,7 +218,7 @@ typedef struct mxs_router_object
* must update these versions numbers in accordance with the rules in
* modinfo.h.
*/
#define MXS_ROUTER_VERSION { 2, 0, 0 }
#define MXS_ROUTER_VERSION { 3, 0, 0 }
/**
* Specifies capabilities specific for routers. Common capabilities

View File

@ -1,5 +1,8 @@
#!/bin/bash
# Install dependencies
npm i
ITEMS=`node maxctrl.js help|awk '/^$/{p=0} {if(p){print $1}}/Commands:/{p=1}'`
TOC=$(for i in $ITEMS

View File

@ -35,7 +35,8 @@ const maxscale_params = [
'auth_read_timeout',
'auth_write_timeout',
'admin_auth',
'admin_log_auth_failures'
'admin_log_auth_failures',
'passive'
]
exports.command = 'alter <command>'

View File

@ -308,6 +308,9 @@ add_test_executable(mysqlmon_failover_stress.cpp mysqlmon_failover_stress mysqlm
# MySQL Monitor switchover stress
add_test_executable(mysqlmon_switchover_stress.cpp mysqlmon_switchover_stress mysqlmon_switchover_stress LABELS mysqlmon REPL_BACKEND)
# Check monitoring and failover when ignore_external_masters is enabled
add_test_executable(mysqlmon_external_master.cpp mysqlmon_external_master mysqlmon_external_master LABELS mysqlmon REPL_BACKEND)
# Test monitor state change events when manually clearing server bits
add_test_executable(false_monitor_state_change.cpp false_monitor_state_change replication LABELS mysqlmon REPL_BACKEND)
@ -594,6 +597,10 @@ add_test_executable(mxs1543.cpp mxs1543 avro LABELS REPL_BACKEND)
# https://jira.mariadb.org/browse/MXS-1585
add_test_executable(mxs1585.cpp mxs1585 mxs1585 LABELS REPL_BACKEND)
# MXS-1643: Too many monitor events are triggered
# https://jira.mariadb.org/browse/MXS-1643
add_test_executable(mxs1643_extra_events.cpp mxs1643_extra_events mxs1643_extra_events LABELS REPL_BACKEND)
# 'namedserverfilter' test
add_test_executable(namedserverfilter.cpp namedserverfilter namedserverfilter LABELS namedserverfilter LIGHT REPL_BACKEND)

View File

@ -31,6 +31,43 @@ int failed_transaction_num = 0;
/** The amount of rows each transaction inserts */
const int N_INSERTS = 100;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
bool sync_servers(MYSQL* master, MYSQL* slave)
{
bool rval = false;
int t = 240;
for (int i = 0; i < t; i++)
{
char master_log_file[80] = "";
char master_log_pos[80] = "";
char slave_log_file[80] = "";
char slave_log_pos[80] = "";
find_field(master, "SHOW MASTER STATUS", "File", master_log_file);
find_field(master, "SHOW MASTER STATUS", "Position", master_log_pos);
find_field(slave, "SHOW SLAVE STATUS", "Master_Log_File", slave_log_file);
find_field(slave, "SHOW SLAVE STATUS", "Read_Master_Log_Pos", slave_log_pos);
if (strcmp(slave_log_file, master_log_file) == 0 && strcmp(slave_log_pos, master_log_pos) == 0)
{
rval = true;
break;
}
else
{
sleep(1);
}
}
if (!rval)
{
printf("WARNING: Slave has not caught up in %d seconds. Test will most likely fail.\n", t);
}
return rval;
}
int transaction(MYSQL * conn, int N)
{
int local_result = 0;
@ -83,6 +120,7 @@ int main(int argc, char *argv[])
Test->repl->connect();
execute_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1;");
Test->repl->sync_slaves();
Test->repl->close_connections();
sleep(5);
@ -91,8 +129,6 @@ int main(int argc, char *argv[])
Test->repl->execute_query_all_nodes((char *) "RESET SLAVE ALL");
Test->repl->execute_query_all_nodes((char *) "RESET MASTER");
Test->repl->verbose = true;
Test->tprintf("Starting binlog configuration\n");
Test->start_binlog(0);
@ -120,11 +156,18 @@ int main(int argc, char *argv[])
sleep(15);
Test->tprintf("Blocking master\n");
Test->repl->block_node(0);
Test->stop_timeout();
pthread_mutex_lock(&mutex);
sleep(180);
sync_servers(Test->repl->nodes[0], Test->repl->nodes[3]);
Test->tprintf("Blocking master");
Test->repl->block_node(0);
pthread_mutex_unlock(&mutex);
for (int i = 0; i < 180 && exit_flag == 0; i++)
{
sleep(i);
}
Test->tprintf("Done! Waiting for thread\n");
exit_flag = 1;
@ -135,10 +178,8 @@ int main(int argc, char *argv[])
char rep[256];
int rep_d;
Test->tprintf("Sleeping to let replication happen\n");
sleep(30);
Test->repl->connect();
sync_servers(Test->repl->nodes[2], Test->repl->nodes[3]);
for (int i_n = 3; i_n < Test->repl->N; i_n++)
{
@ -321,6 +362,8 @@ void *transaction_thread( void *ptr )
while ((exit_flag == 0) && i_trans < trans_max)
{
pthread_mutex_lock(&mutex);
trans_result = transaction(conn, i_trans);
if (trans_result != 0)
{
@ -328,7 +371,7 @@ void *transaction_thread( void *ptr )
failed_transaction_num = i_trans;
Test->tprintf("Closing connection\n");
mysql_close(conn);
Test->tprintf("Waiting for repication\n");
Test->tprintf("Waiting for replication");
sleep(15);
Test->tprintf("Calling select_new_master()\n");
select_new_master(Test);
@ -341,8 +384,11 @@ void *transaction_thread( void *ptr )
i_trans--;
}
i_trans++;
pthread_mutex_unlock(&mutex);
}
i_trans--;
exit_flag = 1;
return NULL;
}

View File

@ -0,0 +1,67 @@
[maxscale]
threads=###threads###
[MySQL-Monitor]
type=monitor
module=mysqlmon
servers= server1,server2,server3,server4
user=maxskysql
passwd= skysql
monitor_interval=1000
detect_standalone_master=true
failcount=1
allow_cluster_recovery=true
replication_user=repl
replication_password=repl
backend_connect_timeout=3
backend_read_timeout=3
backend_write_timeout=3
auto_failover=true
auto_rejoin=true
[RW Split Router]
type=service
router= readwritesplit
servers=server1,server2,server3,server4
user=maxskysql
passwd=skysql
[RW Split Listener]
type=listener
service=RW Split Router
protocol=MySQLClient
port=4006
[CLI]
type=service
router=cli
[CLI Listener]
type=listener
service=CLI
protocol=maxscaled
socket=default
[server1]
type=server
address=###node_server_IP_1###
port=###node_server_port_1###
protocol=MySQLBackend
[server2]
type=server
address=###node_server_IP_2###
port=###node_server_port_2###
protocol=MySQLBackend
[server3]
type=server
address=###node_server_IP_3###
port=###node_server_port_3###
protocol=MySQLBackend
[server4]
type=server
address=###node_server_IP_4###
port=###node_server_port_4###
protocol=MySQLBackend

View File

@ -0,0 +1,59 @@
[maxscale]
threads=###threads###
[MySQL-Monitor]
type=monitor
module=mysqlmon
servers=server1,server2,server3
user=maxskysql
passwd=skysql
monitor_interval=1000
detect_standalone_master=true
failcount=1
allow_cluster_recovery=true
auto_failover=true
auto_rejoin=true
replication_user=repl
replication_password=repl
ignore_external_masters=true
[RW Split Router]
type=service
router=readwritesplit
servers=server1,server2,server3
user=maxskysql
passwd=skysql
[RW Split Listener]
type=listener
service=RW Split Router
protocol=MySQLClient
port=4006
[CLI]
type=service
router=cli
[CLI Listener]
type=listener
service=CLI
protocol=maxscaled
socket=default
[server1]
type=server
address=###node_server_IP_1###
port=###node_server_port_1###
protocol=MySQLBackend
[server2]
type=server
address=###node_server_IP_2###
port=###node_server_port_2###
protocol=MySQLBackend
[server3]
type=server
address=###node_server_IP_3###
port=###node_server_port_3###
protocol=MySQLBackend

View File

@ -8,18 +8,7 @@ using std::endl;
void replicate_from(TestConnections& test, int server_ind, int target_ind)
{
stringstream change_master;
change_master << "CHANGE MASTER TO MASTER_HOST = '" << test.repl->IP[target_ind]
<< "', MASTER_PORT = " << test.repl->port[target_ind] << ", MASTER_USE_GTID = current_pos, "
"MASTER_USER='repl', MASTER_PASSWORD='repl';";
cout << "Server " << server_ind + 1 << " starting to replicate from server " << target_ind + 1 << endl;
if (test.verbose)
{
cout << "Query is '" << change_master.str() << "'" << endl;
}
execute_query(test.repl->nodes[server_ind], "STOP SLAVE;");
execute_query(test.repl->nodes[server_ind], change_master.str().c_str());
execute_query(test.repl->nodes[server_ind], "START SLAVE;");
test.repl->replicate_from(server_ind, target_ind);
}
void reset_replication(TestConnections& test)

View File

@ -79,7 +79,13 @@ void get_maxscale_ips(TestConnections& test, vector<string>* pIps)
to_collection(output, "\n", pIps);
transform(pIps->begin(), pIps->end(), pIps->begin(), extract_ip);
pIps->erase(find(pIps->begin(), pIps->end(), "127.0.0.1"));
// Remove 127.0.0.1 if it is present.
auto i = find(pIps->begin(), pIps->end(), "127.0.0.1");
if (i != pIps->end())
{
pIps->erase(i);
}
}
}

View File

@ -15,6 +15,7 @@
#include <climits>
#include <string>
#include <sstream>
#include <iostream>
#include <vector>
namespace
@ -385,6 +386,7 @@ int Mariadb_nodes::start_replication()
for (int i = 0; i < N; i++)
{
execute_query(nodes[i], "SET GLOBAL read_only=OFF");
execute_query(nodes[i], "STOP SLAVE;");
if (g_require_gtid)
@ -551,6 +553,20 @@ int Mariadb_nodes::unblock_all_nodes()
return rval;
}
bool is_readonly(MYSQL* conn)
{
bool rval = false;
char output[512];
find_field(conn, "SHOW VARIABLES LIKE 'read_only'", "Value", output);
if (strcasecmp(output, "OFF") != 0)
{
rval = true;
}
return rval;
}
bool Mariadb_nodes::check_master_node(MYSQL *conn)
{
bool rval = true;
@ -600,10 +616,7 @@ bool Mariadb_nodes::check_master_node(MYSQL *conn)
}
}
char output[512];
find_field(conn, "SHOW VARIABLES LIKE 'read_only'", "Value", output);
if (strcmp(output, "OFF"))
if (is_readonly(conn))
{
printf("The master is in read-only mode\n");
rval = false;
@ -754,7 +767,8 @@ int Mariadb_nodes::check_replication()
else if (bad_slave_thread_status(nodes[i], "Slave_IO_Running", i) ||
bad_slave_thread_status(nodes[i], "Slave_SQL_Running", i) ||
wrong_replication_type(nodes[i]) ||
multi_source_replication(nodes[i], i))
multi_source_replication(nodes[i], i) ||
is_readonly(nodes[i]))
{
res = 1;
if (verbose)
@ -1444,3 +1458,21 @@ int Mariadb_nodes::prepare_servers()
}
return rval;
}
void Mariadb_nodes::replicate_from(int slave, int master, const char* type)
{
std::stringstream change_master;
change_master << "CHANGE MASTER TO MASTER_HOST = '" << IP[master]
<< "', MASTER_PORT = " << port[master] << ", MASTER_USE_GTID = " << type << ", "
"MASTER_USER='repl', MASTER_PASSWORD='repl';";
if (verbose)
{
std::cout << "Server " << slave + 1 << " starting to replicate from server " << master + 1 << std::endl;
std::cout << "Query is '" << change_master.str() << "'" << std::endl;
}
execute_query(nodes[slave], "STOP SLAVE;");
execute_query(nodes[slave], change_master.str().c_str());
execute_query(nodes[slave], "START SLAVE;");
}

View File

@ -430,6 +430,18 @@ public:
/** Whether to require GTID based replication, defaults to false */
static void require_gtid(bool value);
/**
* Configure a server as a slave of another server
*
* The servers are configured with GTID replicating using the configured
* GTID position, either slave_pos or current_pos.
*
* @param slave The node index to assign as slave
* @param master The node index of the master
* @param type Replication type
*/
void replicate_from(int slave, int master, const char* type = "current_pos");
private:
bool check_master_node(MYSQL *conn);

View File

@ -25,28 +25,6 @@ void change_master(TestConnections& test, int slave, int master, const char* nam
source.c_str(), test.repl->IP[master], test.repl->user_name, test.repl->password, source.c_str());
}
std::string dump_status(const StringSet& current, const StringSet& expected)
{
std::stringstream ss;
ss << "Current status: (";
for (const auto& a: current)
{
ss << a << ",";
}
ss << ") Expected status: (";
for (const auto& a: expected)
{
ss << a << ",";
}
ss << ")";
return ss.str();
}
void check_status(TestConnections& test, const StringSet& expected_master, const StringSet& expected_slave)
{
sleep(2);

View File

@ -0,0 +1,39 @@
/**
* MXS-1643: Too many monitor events are triggered
*
* https://jira.mariadb.org/browse/MXS-1643
*/
#include "testconnections.h"
int main(int argc, char** argv)
{
Mariadb_nodes::require_gtid(true);
TestConnections test(argc, argv);
// Check that master gets the slave status when set into read-only mode
test.tprintf("Set master into read-only mode");
test.repl->connect();
execute_query(test.repl->nodes[0], "SET GLOBAL read_only=ON");
sleep(10);
test.tprintf("Check that the current master now has the slave label");
test.check_log_err(0, "[Master, Running] -> [Running]", false);
test.check_log_err(0, "[Master, Running] -> [Slave, Running]", true);
execute_query(test.repl->nodes[0], "SET GLOBAL read_only=OFF");
sleep(5);
test.maxscales->ssh_node_f(0, true, "truncate -s 0 /var/log/maxscale/maxscale.log");
// Check that the Master and Slave status aren't both set
test.tprintf("Block master and wait for monitor to detect it.");
test.repl->block_node(0);
sleep(10);
test.tprintf("Check that the new master doesn't have both slave and master labels");
test.check_log_err(0, "[Slave, Running] -> [Master, Slave, Running]", false);
test.check_log_err(0, "[Slave, Running] -> [Master, Running]", true);
test.repl->unblock_node(0);
test.tprintf("Cleanup");
test.repl->execute_query_all_nodes( "STOP ALL SLAVES; RESET SLAVE ALL;");
test.repl->fix_replication();
return test.global_result;
}

View File

@ -0,0 +1,114 @@
/**
* Test monitoring and failover with ignore_external_masters=true
*/
#include "testconnections.h"
#include <thread>
#define DOWN "Down"
#define RUNNING "Running"
#define MASTER "Master"
#define SLAVE "Slave"
const StringSet master_running = {MASTER, RUNNING};
const StringSet slave_running = {SLAVE, RUNNING};
const StringSet running = {RUNNING};
const StringSet down = {DOWN};
void check_status(TestConnections& test, const char* server, const StringSet& expected, const char* message)
{
StringSet state = test.get_server_status(server);
test.assert(state == expected, "%s: %s", message, dump_status(state, expected).c_str());
}
static bool is_running = true;
void writer_func(TestConnections* test)
{
while (is_running)
{
MYSQL* conn = open_conn(test->maxscales->rwsplit_port[0], test->maxscales->IP[0],
"test", "test", false);
for (int i = 0; i < 100; i++)
{
if (execute_query_silent(conn, "INSERT INTO test.t1 VALUES (SELECT SLEEP(0.5))"))
{
sleep(1);
break;
}
}
mysql_close(conn);
}
}
int main(int argc, char** argv)
{
Mariadb_nodes::require_gtid(true);
TestConnections test(argc, argv);
// Create a table and a user and start a thread that does writes
test.repl->connect();
execute_query(test.repl->nodes[0], "CREATE OR REPLACE TABLE test.t1 (id INT)");
execute_query(test.repl->nodes[0], "DROP USER IF EXISTS 'test'@'%%'");
execute_query(test.repl->nodes[0], "CREATE USER 'test'@'%%' IDENTIFIED BY 'test'");
execute_query(test.repl->nodes[0], "GRANT INSERT, SELECT, UPDATE, DELETE ON *.* TO 'test'@'%%'");
test.repl->sync_slaves();
std::thread thr(writer_func, &test);
test.tprintf("Start by having the current master replicate from the external server");
test.repl->connect();
test.repl->replicate_from(0, 3);
sleep(5);
check_status(test, "server1", master_running, "server1 should be the master");
check_status(test, "server2", slave_running, "server2 should be a slave");
check_status(test, "server3", slave_running, "server3 should be a slave");
test.tprintf("Stop server1, expect server2 to be promoted as the master");
test.repl->stop_node(0);
sleep(10);
check_status(test, "server1", down, "server1 should be down");
check_status(test, "server2", master_running, "server2 should be the master");
check_status(test, "server3", slave_running, "server3 should be a slave");
test.tprintf("Configure master-master replication between server2 and the external server");
test.repl->replicate_from(1, 3);
test.repl->replicate_from(3, 1);
sleep(10);
check_status(test, "server2", master_running, "server2 should still be the master");
check_status(test, "server3", slave_running, "server3 should be a slave");
test.tprintf("Start server1, expect it to rejoin the cluster");
test.repl->start_node(0);
sleep(10);
check_status(test, "server1", slave_running, "server1 should be a slave");
check_status(test, "server2", master_running, "server2 should still be the master");
check_status(test, "server3", slave_running, "server3 should be a slave");
test.tprintf("Stop server2, expect server1 to be promoted as the master");
test.repl->stop_node(1);
test.repl->connect();
test.repl->replicate_from(0, 3);
test.repl->replicate_from(3, 0);
sleep(10);
check_status(test, "server1", master_running, "server1 should be the master");
check_status(test, "server2", down, "server2 should be down");
check_status(test, "server3", slave_running, "server3 should be a slave");
test.tprintf("Start server2, expect it to rejoin the cluster");
test.repl->start_node(1);
sleep(10);
check_status(test, "server1", master_running, "server1 should still be the master");
check_status(test, "server2", slave_running, "server2 should be a slave");
check_status(test, "server3", slave_running, "server3 should be a slave");
// Cleanup
is_running = false;
thr.join();
execute_query(test.repl->nodes[0], "STOP SLAVE; RESET SLAVE ALL;");
return test.global_result;
}

View File

@ -6,6 +6,7 @@
#include <time.h>
#include <signal.h>
#include <execinfo.h>
#include <sstream>
#include "mariadb_func.h"
#include "maxadmin_operations.h"
@ -819,7 +820,6 @@ int TestConnections::start_binlog(int m)
binlog = open_conn_no_db(maxscales->binlog_port[m], maxscales->IP[m], repl->user_name, repl->password, ssl);
execute_query(binlog, (char *) "stop slave");
execute_query(binlog, (char *) "reset slave all");
execute_query(binlog, (char *) "reset master");
mysql_close(binlog);
tprintf("Stopping maxscale\n");
@ -1112,55 +1112,63 @@ void TestConnections::check_log_err(int m, const char * err_msg, bool expected)
char * err_log_content;
tprintf("Getting logs\n");
if (verbose)
{
tprintf("Getting logs");
}
char sys1[4096];
char dest[1024];
char log_file[64];
set_timeout(100);
set_timeout(500);
sprintf(dest, "maxscale_log_%03d/", m);
sprintf(&sys1[0], "mkdir -p maxscale_log_%03d; rm -f %s*.log",
m, dest);
//tprintf("Executing: %s\n", sys1);
system(sys1);
set_timeout(50);
sprintf(sys1, "%s/*", maxscales->maxscale_log_dir[m]);
maxscales->copy_from_node(m, sys1, dest);
tprintf("Reading maxscale.log\n");
sprintf(log_file, "maxscale_log_%03d/maxscale.log", m);
if ( ( read_log(log_file, &err_log_content) != 0) || (strlen(err_log_content) < 2) )
if (verbose)
{
tprintf("Reading maxscale1.log\n");
tprintf("Reading maxscale.log");
}
sprintf(log_file, "maxscale_log_%03d/maxscale.log", m);
if (read_log(log_file, &err_log_content) != 0 || strlen(err_log_content) < 2)
{
if (verbose)
{
tprintf("Reading maxscale1.log");
}
sprintf(log_file, "maxscale_log_%03d/maxscale1.log", m);
free(err_log_content);
if (read_log(log_file, &err_log_content) != 0)
{
add_result(1, "Error reading log\n");
add_result(1, "Error reading log");
}
}
//printf("\n\n%s\n\n", err_log_content);
if (err_log_content != NULL)
{
if (expected)
{
if (strstr(err_log_content, err_msg) == NULL)
{
add_result(1, "There is NO \"%s\" error in the log\n", err_msg);
add_result(1, "There is NO \"%s\" error in the log", err_msg);
}
else
{
tprintf("There is proper \"%s \" error in the log\n", err_msg);
tprintf("There is a proper \"%s \" error in the log", err_msg);
}
}
else
{
if (strstr(err_log_content, err_msg) != NULL)
{
add_result(1, "There is UNEXPECTED error \"%s\" error in the log\n", err_msg);
add_result(1, "There is an UNEXPECTED \"%s\" error in the log", err_msg);
}
else
{
tprintf("There are no unxpected errors \"%s \" error in the log\n", err_msg);
tprintf("There are no unxpected \"%s \" errors in the log", err_msg);
}
}
@ -1877,3 +1885,25 @@ bool TestConnections::test_bad_config(int m, const char *config)
return maxscales->ssh_node(m, "cp maxscale.cnf /etc/maxscale.cnf; service maxscale stop; "
"maxscale -U maxscale -lstdout &> /dev/null && sleep 1 && pkill -9 maxscale", false) == 0;
}
std::string dump_status(const StringSet& current, const StringSet& expected)
{
std::stringstream ss;
ss << "Current status: (";
for (const auto& a: current)
{
ss << a << ",";
}
ss << ") Expected status: (";
for (const auto& a: expected)
{
ss << a << ",";
}
ss << ")";
return ss.str();
}

View File

@ -531,4 +531,14 @@ void * timeout_thread(void *ptr );
*/
void * log_copy_thread(void *ptr );
/**
* Dump two server status sets as strings
*
* @param current The current status
* @param expected The expected status
*
* @return String form comparison of status sets
*/
std::string dump_status(const StringSet& current, const StringSet& expected);
#endif // TESTCONNECTIONS_H

View File

@ -3,9 +3,9 @@ if (BUILD_QC_MYSQLEMBEDDED)
# Include the embedded library headers
subdirs(MYSQL_INCLUDE_DIR_ALL ${MYSQL_EMBEDDED_INCLUDE_DIR})
foreach(DIR ${MYSQL_INCLUDE_DIR_ALL})
include_directories(${DIR})
include_directories(BEFORE ${DIR})
endforeach()
include_directories(${MYSQL_EMBEDDED_INCLUDE_DIR}/..)
include_directories(BEFORE ${MYSQL_EMBEDDED_INCLUDE_DIR}/..)
add_library(qc_mysqlembedded SHARED qc_mysqlembedded.cc)

View File

@ -3526,7 +3526,7 @@ extern "C"
{
MXS_MODULE_API_QUERY_CLASSIFIER,
MXS_MODULE_IN_DEVELOPMENT,
QUERY_CLASSIFIER_VERSION,
MXS_QUERY_CLASSIFIER_VERSION,
"Query classifier based upon MySQL Embedded",
"V1.0.0",
MXS_NO_MODULE_CAPABILITIES,

View File

@ -5037,7 +5037,7 @@ MXS_MODULE* MXS_CREATE_MODULE()
{
MXS_MODULE_API_QUERY_CLASSIFIER,
MXS_MODULE_BETA_RELEASE,
QUERY_CLASSIFIER_VERSION,
MXS_QUERY_CLASSIFIER_VERSION,
"Query classifier using sqlite.",
"V1.0.0",
MXS_NO_MODULE_CAPABILITIES,

View File

@ -611,6 +611,39 @@ int config_cb(const char* fpath, const struct stat *sb, int typeflag, struct FTW
{
int rval = 0;
if (typeflag == FTW_SL) // A symbolic link; let's see what it points to.
{
struct stat sb;
if (stat(fpath, &sb) == 0)
{
int file_type = (sb.st_mode & S_IFMT);
switch (file_type)
{
case S_IFREG:
// Points to a file; we'll handle that regardless of where the file resides.
typeflag = FTW_F;
break;
case S_IFDIR:
// Points to a directory; we'll ignore that.
MXS_WARNING("Symbolic link %s in configuration directory points to a "
"directory; it will be ignored.", fpath);
break;
default:
// Points to something else; we'll silently ignore.
;
}
}
else
{
MXS_WARNING("Could not get information about the symbolic link %s; "
"it will be ignored.", fpath);
}
}
if (typeflag == FTW_F) // We are only interested in files,
{
const char* filename = fpath + ftwbuf->base;
@ -3651,6 +3684,7 @@ void fix_serverlist(char* value)
dest += sep;
dest += start;
sep = ",";
start = strtok_r(NULL, ",", &end);
}
/** The value will always be smaller than the original one or of equal size */

View File

@ -3204,9 +3204,24 @@ static uint32_t dcb_handler(DCB* dcb, uint32_t events)
static uint32_t dcb_poll_handler(MXS_POLL_DATA *data, int thread_id, uint32_t events)
{
uint32_t rval = 0;
DCB *dcb = (DCB*)data;
return dcb_handler(dcb, events);
/**
* Fake hangup events (e.g. from monitors) can cause a DCB to be closed
* before the real events are processed. This makes it look like a closed
* DCB is receiving events when in reality the events were received at the
* same time the DCB was closed. If a closed DCB receives events they should
* be ignored.
*
* @see FakeEventTask()
*/
if (dcb->n_close == 0)
{
rval = dcb_handler(dcb, events);
}
return rval;
}
static bool dcb_is_still_valid(DCB* target, int id)

View File

@ -43,6 +43,7 @@
#include <maxscale/housekeeper.h>
#include <maxscale/log_manager.h>
#include <maxscale/maxscale.h>
#include <maxscale/mysql_utils.h>
#include <maxscale/paths.h>
#include <maxscale/query_classifier.h>
#include <maxscale/server.h>
@ -188,6 +189,8 @@ static bool modules_process_init();
static void modules_process_finish();
static void disable_module_unloading(const char* arg);
static void enable_module_unloading(const char* arg);
static void enable_statement_logging(const char* arg);
static void disable_statement_logging(const char* arg);
static void redirect_output_to_file(const char* arg);
static bool user_is_acceptable(const char* specified_user);
static bool init_sqlite3();
@ -217,6 +220,14 @@ const DEBUG_ARGUMENT debug_arguments[] =
"redirect-output-to-file", redirect_output_to_file,
"redirect stdout and stderr to the file given as an argument"
},
{
"enable-statement-logging", enable_statement_logging,
"enable the logging of SQL statements sent by MaxScale to the servers"
},
{
"disable-statement-logging", disable_statement_logging,
"disable the logging of SQL statements sent by MaxScale to the servers"
},
{NULL, NULL, NULL}
};
@ -3185,6 +3196,16 @@ static void disable_module_unloading(const char* arg)
unload_modules_at_exit = false;
}
static void enable_statement_logging(const char* arg)
{
mxs_mysql_set_log_statements(true);
}
static void disable_statement_logging(const char* arg)
{
mxs_mysql_set_log_statements(false);
}
static void redirect_output_to_file(const char* arg)
{
if (arg)

View File

@ -32,6 +32,12 @@
#include <maxscale/alloc.h>
#include <maxscale/json_api.h>
#include <maxscale/modulecmd.h>
#include <maxscale/protocol.h>
#include <maxscale/router.h>
#include <maxscale/filter.h>
#include <maxscale/authenticator.h>
#include <maxscale/monitor.h>
#include <maxscale/query_classifier.h>
#include "internal/modules.h"
#include "internal/config.h"
@ -78,6 +84,56 @@ static LOADED_MODULE* register_module(const char *module,
MXS_MODULE *mod_info);
static void unregister_module(const char *module);
static bool api_version_mismatch(const MXS_MODULE *mod_info, const char* module)
{
bool rval = false;
MXS_MODULE_VERSION api = {};
switch (mod_info->modapi)
{
case MXS_MODULE_API_PROTOCOL:
api = MXS_PROTOCOL_VERSION;
break;
case MXS_MODULE_API_AUTHENTICATOR:
api = MXS_AUTHENTICATOR_VERSION;
break;
case MXS_MODULE_API_ROUTER:
api = MXS_ROUTER_VERSION;
break;
case MXS_MODULE_API_MONITOR:
api = MXS_MONITOR_VERSION;
break;
case MXS_MODULE_API_FILTER:
api = MXS_FILTER_VERSION;
break;
case MXS_MODULE_API_QUERY_CLASSIFIER:
api = MXS_QUERY_CLASSIFIER_VERSION;
break;
default:
MXS_ERROR("Unknown module type: 0x%02hhx", mod_info->modapi);
ss_dassert(!true);
break;
}
if (api.major != mod_info->api_version.major ||
api.minor != mod_info->api_version.minor ||
api.patch != mod_info->api_version.patch)
{
MXS_ERROR("API version mismatch for '%s': Need version %d.%d.%d, have %d.%d.%d",
module, api.major, api.minor, api.patch, mod_info->api_version.major,
mod_info->api_version.minor, mod_info->api_version.patch);
rval = true;
}
return rval;
}
static bool check_module(const MXS_MODULE *mod_info, const char *type, const char *module)
{
bool success = true;
@ -118,6 +174,12 @@ static bool check_module(const MXS_MODULE *mod_info, const char *type, const cha
MXS_ERROR("Module '%s' does not implement the query classifier API.", module);
success = false;
}
if (api_version_mismatch(mod_info, module))
{
success = false;
}
if (mod_info->version == NULL)
{
MXS_ERROR("Module '%s' does not define a version string", module);

View File

@ -2428,7 +2428,7 @@ static bool journal_is_stale(MXS_MONITOR *monitor, time_t max_age)
return is_stale;
}
MXS_MONITORED_SERVER* mon_get_monitored_server(MXS_MONITOR* mon, SERVER* search_server)
MXS_MONITORED_SERVER* mon_get_monitored_server(const MXS_MONITOR* mon, SERVER* search_server)
{
ss_dassert(mon && search_server);
for (MXS_MONITORED_SERVER* iter = mon->monitored_servers; iter != NULL; iter = iter->next)
@ -2439,4 +2439,46 @@ MXS_MONITORED_SERVER* mon_get_monitored_server(MXS_MONITOR* mon, SERVER* search_
}
}
return NULL;
}
int mon_config_get_servers(const MXS_CONFIG_PARAMETER* params, const char* key, const MXS_MONITOR* mon,
MXS_MONITORED_SERVER*** monitored_servers_out)
{
ss_dassert(*monitored_servers_out == NULL);
SERVER** servers = NULL;
int servers_size = config_get_server_list(params, key, &servers);
int rval = 0;
// All servers in the array must be monitored by the given monitor.
if (servers_size > 0)
{
MXS_MONITORED_SERVER** monitored_array =
(MXS_MONITORED_SERVER**)MXS_CALLOC(servers_size, sizeof(MXS_MONITORED_SERVER*));
bool error = false;
for (int i = 0; i < servers_size && !error; i++)
{
MXS_MONITORED_SERVER* mon_serv = mon_get_monitored_server(mon, servers[i]);
if (mon_serv != NULL)
{
monitored_array[i] = mon_serv;
}
else
{
MXS_ERROR("Server '%s' is not monitored by monitor '%s'.", servers[i]->unique_name, mon->name);
error = true;
}
}
MXS_FREE(servers);
if (error)
{
MXS_FREE(monitored_array);
rval = -1;
}
else
{
*monitored_servers_out = monitored_array;
rval = servers_size;
}
}
return rval;
}

View File

@ -32,6 +32,21 @@
#include <maxscale/debug.h>
#include <maxscale/log_manager.h>
namespace
{
struct THIS_UNIT
{
bool log_statements; // Should all statements sent to server be logged?
};
static THIS_UNIT this_unit =
{
false
};
}
/**
* @brief Calculate the length of a length-encoded integer in bytes
*
@ -221,6 +236,19 @@ int mxs_mysql_query(MYSQL* conn, const char* query)
rc = mysql_query(conn, query);
}
if (this_unit.log_statements)
{
const char* host;
if (mariadb_get_info(conn, MARIADB_CONNECTION_HOST, &host) != 0)
{
// No idea about the host, but let's use something that looks like
// an IP-address as a placeholder.
host = "0.0.0.0";
}
MXS_NOTICE("SQL(%s): %d, \"%s\"", host, rc, query);
}
return rc;
}
@ -377,3 +405,13 @@ void mxs_mysql_set_server_version(MYSQL* mysql, SERVER* server)
}
}
}
void mxs_mysql_set_log_statements(bool enable)
{
this_unit.log_statements = enable;
}
bool mxs_mysql_get_log_statements()
{
return this_unit.log_statements;
}

View File

@ -31,6 +31,7 @@
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <sstream>
#include <sys/time.h>
#include <fstream>
#include <sstream>

View File

@ -85,6 +85,12 @@ enum slave_down_setting_t
REJECT_DOWN
};
enum print_repl_warnings_t
{
WARNINGS_ON,
WARNINGS_OFF
};
static void monitorMain(void *);
static void *startMonitor(MXS_MONITOR *, const MXS_CONFIG_PARAMETER*);
static void stopMonitor(MXS_MONITOR *);
@ -132,6 +138,7 @@ static const char CN_FAILOVER_TIMEOUT[] = "failover_timeout";
static const char CN_SWITCHOVER_TIMEOUT[] = "switchover_timeout";
static const char CN_AUTO_REJOIN[] = "auto_rejoin";
static const char CN_FAILCOUNT[] = "failcount";
static const char CN_NO_PROMOTE_SERVERS[] = "servers_no_promotion";
// Parameters for master failure verification and timeout
static const char CN_VERIFY_MASTER_FAILURE[] = "verify_master_failure";
@ -893,6 +900,7 @@ extern "C"
{CN_VERIFY_MASTER_FAILURE, MXS_MODULE_PARAM_BOOL, "true"},
{CN_MASTER_FAILURE_TIMEOUT, MXS_MODULE_PARAM_COUNT, DEFAULT_MASTER_FAILURE_TIMEOUT},
{CN_AUTO_REJOIN, MXS_MODULE_PARAM_BOOL, "false"},
{CN_NO_PROMOTE_SERVERS, MXS_MODULE_PARAM_SERVERLIST},
{MXS_END_MODULE_PARAMS}
}
};
@ -985,7 +993,24 @@ static bool set_replication_credentials(MYSQL_MONITOR *handle, const MXS_CONFIG_
return rval;
}
/*lint +e14 */
/**
* Is the server in the excluded list
*
* @param handle Cluster monitor
* @param server Server to test
* @return True if server is in the excluded-list of the monitor.
*/
static bool server_is_excluded(const MYSQL_MONITOR *handle, const MXS_MONITORED_SERVER* server)
{
for (int i = 0; i < handle->n_excluded; i++)
{
if (handle->excluded_servers[i] == server)
{
return true;
}
}
return false;
}
/**
* Start the instance of the monitor, returning a handle on the monitor.
@ -999,14 +1024,17 @@ static bool set_replication_credentials(MYSQL_MONITOR *handle, const MXS_CONFIG_
static void *
startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params)
{
bool error = false;
MYSQL_MONITOR *handle = (MYSQL_MONITOR*) monitor->handle;
if (handle)
{
handle->shutdown = 0;
MXS_FREE(handle->script);
MXS_FREE(handle->replication_user);
MXS_FREE(handle->replication_password);
MXS_FREE(handle->excluded_servers);
handle->excluded_servers = NULL;
handle->n_excluded = 0;
}
else
{
@ -1052,7 +1080,13 @@ startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params)
handle->master_failure_timeout = config_get_integer(params, CN_MASTER_FAILURE_TIMEOUT);
handle->auto_rejoin = config_get_bool(params, CN_AUTO_REJOIN);
bool error = false;
handle->excluded_servers = NULL;
handle->n_excluded = mon_config_get_servers(params, CN_NO_PROMOTE_SERVERS, monitor,
&handle->excluded_servers);
if (handle->n_excluded < 0)
{
error = true;
}
if (!set_replication_credentials(handle, params))
{
@ -1075,6 +1109,7 @@ startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params)
{
hashtable_free(handle->server_info);
MXS_FREE(handle->script);
MXS_FREE(handle->excluded_servers);
MXS_FREE(handle);
handle = NULL;
}
@ -1167,7 +1202,10 @@ static void diagnostics(DCB *dcb, const MXS_MONITOR *mon)
dcb_printf(dcb, "Master ID: %" PRId64 "\n", serv_info->slave_status.master_server_id);
dcb_printf(dcb, "Master binlog file: %s\n", serv_info->slave_status.master_log_file.c_str());
dcb_printf(dcb, "Master binlog position: %lu\n", serv_info->slave_status.read_master_log_pos);
if (serv_info->slave_status.gtid_io_pos.server_id != SERVER_ID_UNKNOWN)
{
dcb_printf(dcb, "Gtid_IO_Pos: %s\n", serv_info->slave_status.gtid_io_pos.to_string().c_str());
}
if (handle->multimaster)
{
dcb_printf(dcb, "Master group: %d\n", serv_info->group);
@ -1229,7 +1267,11 @@ static json_t* diagnostics_json(const MXS_MONITOR *mon)
json_string(serv_info->slave_status.master_log_file.c_str()));
json_object_set_new(srv, "master_binlog_position",
json_integer(serv_info->slave_status.read_master_log_pos));
if (serv_info->slave_status.gtid_io_pos.server_id != SERVER_ID_UNKNOWN)
{
json_object_set_new(srv, "gtid_io_pos",
json_string(serv_info->slave_status.gtid_io_pos.to_string().c_str()));
}
if (handle->multimaster)
{
json_object_set_new(srv, "master_group", json_integer(serv_info->group));
@ -1953,8 +1995,10 @@ bool standalone_master_required(MYSQL_MONITOR *handle, MXS_MONITORED_SERVER *db)
* @param handle Monitor instance
* @param db Monitor servers
*/
void set_standalone_master(MYSQL_MONITOR *handle, MXS_MONITORED_SERVER *db)
bool set_standalone_master(MYSQL_MONITOR *handle, MXS_MONITORED_SERVER *db)
{
bool rval = false;
while (db)
{
if (SERVER_IS_RUNNING(db->server))
@ -1972,6 +2016,7 @@ void set_standalone_master(MYSQL_MONITOR *handle, MXS_MONITORED_SERVER *db)
monitor_set_pending_status(db, SERVER_MASTER | SERVER_STALE_STATUS);
monitor_clear_pending_status(db, SERVER_SLAVE);
handle->master = db;
rval = true;
}
else if (!handle->allow_cluster_recovery)
{
@ -1980,6 +2025,8 @@ void set_standalone_master(MYSQL_MONITOR *handle, MXS_MONITORED_SERVER *db)
}
db = db->next;
}
return rval;
}
bool failover_not_possible(MYSQL_MONITOR* handle)
@ -2294,8 +2341,12 @@ monitorMain(void *arg)
{
if (standalone_master_required(handle, mon->monitored_servers))
{
/** Other servers have died, set last remaining server as master */
set_standalone_master(handle, mon->monitored_servers);
// Other servers have died, set last remaining server as master
if (set_standalone_master(handle, mon->monitored_servers))
{
// Update the root_master to point to the standalone master
root_master = handle->master;
}
}
else
{
@ -2303,9 +2354,10 @@ monitorMain(void *arg)
}
}
if (root_master)
if (root_master && SERVER_IS_MASTER(root_master->server))
{
// Clear slave and stale slave status bits from current master
server_clear_status_nolock(root_master->server, SERVER_SLAVE | SERVER_STALE_SLAVE);
monitor_clear_pending_status(root_master, SERVER_SLAVE | SERVER_STALE_SLAVE);
/**
@ -2313,16 +2365,18 @@ monitorMain(void *arg)
* This allows parts of a multi-tiered replication setup to be used
* in MaxScale.
*/
if (SERVER_IS_SLAVE_OF_EXTERNAL_MASTER(root_master->server) &&
SERVER_IS_MASTER(root_master->server) && handle->ignore_external_masters)
if (handle->ignore_external_masters)
{
monitor_clear_pending_status(root_master,
SERVER_SLAVE | SERVER_SLAVE_OF_EXTERNAL_MASTER);
server_clear_status_nolock(root_master->server,
SERVER_SLAVE | SERVER_SLAVE_OF_EXTERNAL_MASTER);
monitor_clear_pending_status(root_master, SERVER_SLAVE_OF_EXTERNAL_MASTER);
server_clear_status_nolock(root_master->server, SERVER_SLAVE_OF_EXTERNAL_MASTER);
}
}
ss_dassert(handle->master == root_master);
ss_dassert(!root_master ||
((root_master->server->status & (SERVER_SLAVE | SERVER_MASTER))
!= (SERVER_SLAVE | SERVER_MASTER)));
/**
* After updating the status of all servers, check if monitor events
* need to be launched.
@ -3262,20 +3316,25 @@ static MySqlServerInfo* update_slave_info(MYSQL_MONITOR* mon, MXS_MONITORED_SERV
*
* @param server Server to check
* @param server_info Server info
* @param print_on Print warnings or not
* @return True if log_bin is on
*/
static bool check_replication_settings(const MXS_MONITORED_SERVER* server, MySqlServerInfo* server_info)
static bool check_replication_settings(const MXS_MONITORED_SERVER* server, MySqlServerInfo* server_info,
print_repl_warnings_t print_warnings = WARNINGS_ON)
{
bool rval = true;
const char* servername = server->server->unique_name;
if (server_info->rpl_settings.log_bin == false)
{
const char NO_BINLOG[] =
"Slave '%s' has binary log disabled and is not a valid promotion candidate.";
MXS_WARNING(NO_BINLOG, servername);
if (print_warnings == WARNINGS_ON)
{
const char NO_BINLOG[] =
"Slave '%s' has binary log disabled and is not a valid promotion candidate.";
MXS_WARNING(NO_BINLOG, servername);
}
rval = false;
}
else
else if (print_warnings == WARNINGS_ON)
{
if (server_info->rpl_settings.gtid_strict_mode == false)
{
@ -3334,86 +3393,132 @@ bool switchover_check_preferred_master(MYSQL_MONITOR* mon, MXS_MONITORED_SERVER*
return rval;
}
/**
* Is the candidate a better choice for master than the previous best?
*
* @param current_best_info Server info of current best choice
* @param candidate_info Server info of new candidate
* @return True if candidate is better
*/
bool is_candidate_better(const MySqlServerInfo* current_best_info, const MySqlServerInfo* candidate_info)
{
uint64_t cand_io = candidate_info->slave_status.gtid_io_pos.sequence;
uint64_t cand_processed = candidate_info->gtid_current_pos.sequence;
uint64_t curr_io = current_best_info->slave_status.gtid_io_pos.sequence;
uint64_t curr_processed = current_best_info->gtid_current_pos.sequence;
bool cand_updates = candidate_info->rpl_settings.log_slave_updates;
bool curr_updates = current_best_info->rpl_settings.log_slave_updates;
bool is_better = false;
// Accept a slave with a later event in relay log.
if (cand_io > curr_io)
{
is_better = true;
}
// If io sequences are identical, the slave with more events processed wins.
else if (cand_io == curr_io)
{
if (cand_processed > curr_processed)
{
is_better = true;
}
// Finally, if binlog positions are identical, prefer a slave with log_slave_updates.
else if (cand_processed == curr_processed && cand_updates && !curr_updates)
{
is_better = true;
}
}
return is_better;
}
/**
* Select a new master. Also add slaves which should be redirected to an array.
*
* @param mon The monitor
* @param out_slaves Vector for storing slave servers, can be NULL
* @param out_slaves Vector for storing slave servers.
* @param err_out json object for error printing. Can be NULL.
* @return The found master, or NULL if not found
*/
MXS_MONITORED_SERVER* select_new_master(MYSQL_MONITOR* mon,
ServerVector* slaves_out,
json_t** err_out)
MXS_MONITORED_SERVER* select_new_master(MYSQL_MONITOR* mon, ServerVector* slaves_out, json_t** err_out)
{
ss_dassert(slaves_out && slaves_out->size() == 0);
/* Select a new master candidate. Selects the one with the latest event in relay log.
* If multiple slaves have same number of events, select the one with most processed events. */
MXS_MONITORED_SERVER* new_master = NULL;
MySqlServerInfo* new_master_info = NULL;
MXS_MONITORED_SERVER* current_best = NULL;
MySqlServerInfo* current_best_info = NULL;
// Servers that cannot be selected because of exclusion, but seem otherwise ok.
ServerVector valid_but_excluded;
// Index of the current best candidate in slaves_out
int master_vector_index = -1;
for (MXS_MONITORED_SERVER *cand = mon->monitor->monitored_servers; cand; cand = cand->next)
{
// If a server cannot be connected to, it won't be considered for promotion or redirected.
// Do not worry about the exclusion list yet, querying the excluded servers is ok.
MySqlServerInfo* cand_info = update_slave_info(mon, cand);
if (cand_info)
{
if (slaves_out)
slaves_out->push_back(cand);
// Check that server is not in the exclusion list while still being a valid choice.
if (server_is_excluded(mon, cand) && check_replication_settings(cand, cand_info, WARNINGS_OFF))
{
slaves_out->push_back(cand);
valid_but_excluded.push_back(cand);
const char CANNOT_SELECT[] = "Promotion candidate '%s' is excluded from new "
"master selection.";
MXS_INFO(CANNOT_SELECT, cand->server->unique_name);
}
if (check_replication_settings(cand, cand_info))
else if (check_replication_settings(cand, cand_info))
{
bool select_this = false;
// If no candidate yet, accept any slave. Slaves have already been checked to use gtid.
if (new_master == NULL)
// If no new master yet, accept any valid candidate. Otherwise check.
if (current_best == NULL || is_candidate_better(current_best_info, cand_info))
{
select_this = true;
}
else
{
uint64_t cand_io = cand_info->slave_status.gtid_io_pos.sequence;
uint64_t cand_processed = cand_info->gtid_current_pos.sequence;
uint64_t master_io = new_master_info->slave_status.gtid_io_pos.sequence;
uint64_t master_processed = new_master_info->gtid_current_pos.sequence;
bool cand_updates = cand_info->rpl_settings.log_slave_updates;
bool master_updates = new_master_info->rpl_settings.log_slave_updates;
// Otherwise accept a slave with a later event in relay log.
if (cand_io > master_io ||
// If io sequences are identical, the slave with more events processed wins.
(cand_io == master_io && (cand_processed > master_processed ||
// Finally, if binlog positions are identical,
// prefer a slave with log_slave_updates.
(cand_processed == master_processed &&
cand_updates && !master_updates))))
{
select_this = true;
}
}
if (select_this)
{
new_master = cand;
new_master_info = cand_info;
if (slaves_out)
{
master_vector_index = slaves_out->size() - 1;
}
// The server has been selected for promotion, for now.
current_best = cand;
current_best_info = cand_info;
master_vector_index = slaves_out->size() - 1;
}
}
}
}
if (new_master && slaves_out)
if (current_best)
{
// Remove the selected master from the vector.
ServerVector::iterator remove_this = slaves_out->begin();
remove_this += master_vector_index;
slaves_out->erase(remove_this);
}
if (new_master == NULL)
// Check if any of the excluded servers would be better than the best candidate.
for (ServerVector::const_iterator iter = valid_but_excluded.begin();
iter != valid_but_excluded.end();
iter++)
{
MySqlServerInfo* excluded_info = get_server_info(mon, *iter);
const char* excluded_name = (*iter)->server->unique_name;
if (current_best == NULL)
{
const char EXCLUDED_ONLY_CAND[] = "Server '%s' is a viable choice for new master, "
"but cannot be selected as it's excluded.";
MXS_WARNING(EXCLUDED_ONLY_CAND, excluded_name);
break;
}
else if (is_candidate_better(current_best_info, excluded_info))
{
// Print a warning if this server is actually a better candidate than the previous
// best.
const char EXCLUDED_CAND[] = "Server '%s' is superior to current "
"best candidate '%s', but cannot be selected as it's excluded. This may lead to "
"loss of data if '%s' is ahead of other servers.";
MXS_WARNING(EXCLUDED_CAND, excluded_name, current_best->server->unique_name, excluded_name);
break;
}
}
if (current_best == NULL)
{
PRINT_MXS_JSON_ERROR(err_out, "No suitable promotion candidate found.");
}
return new_master;
return current_best;
}
/**

View File

@ -73,6 +73,9 @@ typedef struct
int64_t master_gtid_domain; /**< Gtid domain currently used by the master */
bool auto_rejoin; /**< Attempt to start slave replication on standalone servers or servers
replicating from the wrong master. */
int n_excluded; /**< Number of excluded servers */
MXS_MONITORED_SERVER** excluded_servers; /**< Servers banned for master promotion during auto-failover. */
MXS_MONITOR* monitor;
} MYSQL_MONITOR;

View File

@ -1276,8 +1276,6 @@ static void clientReply(MXS_ROUTER *instance,
*/
if (backend->session_command_count())
{
check_session_command_reply(writebuf, backend);
/** This discards all responses that have already been sent to the client */
bool rconn = false;
process_sescmd_response(rses, backend, &writebuf, &rconn);

View File

@ -283,34 +283,6 @@ void closed_session_reply(GWBUF *querybuf)
}
}
/*
* Uses MySQL specific mechanisms
*/
/**
* @brief Check the reply from a backend server to a session command
*
* If the reply is an error, a message is logged.
*
* @param buffer Query buffer containing reply data
* @param backend Router session data for a backend server
*/
void check_session_command_reply(GWBUF *buffer, SRWBackend& backend)
{
if (MYSQL_IS_ERROR_PACKET(((uint8_t *)GWBUF_DATA(buffer))))
{
size_t replylen = MYSQL_GET_PAYLOAD_LEN(GWBUF_DATA(buffer));
char replybuf[replylen];
gwbuf_copy_data(buffer, 0, gwbuf_length(buffer), (uint8_t*)replybuf);
std::string err;
std::string msg;
err.append(replybuf + 8, 5);
msg.append(replybuf + 13, replylen - 4 - 5);
MXS_ERROR("Failed to execute session command in %s. Error was: %s %s",
backend->uri(), err.c_str(), msg.c_str());
}
}
/**
* @brief Send an error message to the client telling that the server is in read only mode
*

View File

@ -1045,11 +1045,17 @@ static void log_master_routing_failure(RWSplitSession *rses, bool found,
sprintf(errmsg, "Session is in read-only mode because it was created "
"when no master was available");
}
else if (old_master && !old_master->in_use())
{
sprintf(errmsg, "Was supposed to route to master but the master connection is %s",
old_master->is_closed() ? "closed" : "not in a suitable state");
ss_dassert(old_master->is_closed());
}
else
{
ss_dassert(false); // A session should always have a master reference
sprintf(errmsg, "Was supposed to route to master but couldn't "
"find master in a suitable state");
"find original master connection");
ss_dassert(!true);
}
}

View File

@ -76,9 +76,10 @@ void process_sescmd_response(RWSplitSession* rses, SRWBackend& backend,
if (rses->sescmd_responses[id] != cmd)
{
MXS_ERROR("Slave server '%s': response differs from master's response. "
"Closing connection due to inconsistent session state.",
backend->name());
MXS_WARNING("Slave server '%s': response (0x%02hhx) differs "
"from master's response(0x%02hhx). Closing slave "
"connection due to inconsistent session state.",
backend->name(), cmd, rses->sescmd_responses[id]);
backend->close(mxs::Backend::CLOSE_FATAL);
*pReconnect = true;
}