diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md
index 4bb672149..a225cd41a 100644
--- a/Documentation/Changelog.md
+++ b/Documentation/Changelog.md
@@ -49,6 +49,7 @@ For more details, please refer to:
* MaxScale now supports IPv6
For more details, please refer to:
+* [MariaDB MaxScale 2.1.12 Release Notes](Release-Notes/MaxScale-2.1.12-Release-Notes.md)
* [MariaDB MaxScale 2.1.11 Release Notes](Release-Notes/MaxScale-2.1.11-Release-Notes.md)
* [MariaDB MaxScale 2.1.10 Release Notes](Release-Notes/MaxScale-2.1.10-Release-Notes.md)
* [MariaDB MaxScale 2.1.9 Release Notes](Release-Notes/MaxScale-2.1.9-Release-Notes.md)
diff --git a/Documentation/Monitors/MySQL-Monitor.md b/Documentation/Monitors/MySQL-Monitor.md
index 47a6ddd82..74e6adbfa 100644
--- a/Documentation/Monitors/MySQL-Monitor.md
+++ b/Documentation/Monitors/MySQL-Monitor.md
@@ -120,6 +120,23 @@ This functionality is similar to the [Multi-Master Monitor](MM-Monitor.md)
functionality. The only difference is that the MySQL monitor will also detect
traditional Master-Slave topologies.
+### `ignore_external_masters`
+
+Ignore any servers that are not monitored by this monitor but are a part of the
+replication topology. This option was added in MaxScale 2.1.12 and is disabled
+by default.
+
+MaxScale detects if a master server replicates from an external server. When
+this is detected, the server is assigned the `Slave` and `Slave of External
+Server` labels and will be treated as a slave server. Most of the time this
+topology is used when MaxScale is used for read scale-out without master
+servers, a Galera cluster with read replicas being a prime example of this
+setup. Sometimes this is not the desired behavior and the external master server
+should be ignored. Most of the time this is due to multi-source replication.
+
+When this option is enabled, all servers that have the `Master, Slave, Slave of
+External Server, Running` labels will instead get the `Master, Running` labels.
+
### `detect_standalone_master`
Detect standalone master servers. This feature takes a boolean parameter and is
diff --git a/Documentation/Release-Notes/MaxScale-2.1.12-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.12-Release-Notes.md
index 850f3cdc8..3c91b9b71 100644
--- a/Documentation/Release-Notes/MaxScale-2.1.12-Release-Notes.md
+++ b/Documentation/Release-Notes/MaxScale-2.1.12-Release-Notes.md
@@ -36,6 +36,13 @@ where only parameters are used with the binlogrouter.
[Here is a list of bugs fixed in MaxScale 2.1.12.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.1.12)
+* [MXS-1555](https://jira.mariadb.org/browse/MXS-1555) Protocol command tracking doesn't work with readwritesplit
+* [MXS-1553](https://jira.mariadb.org/browse/MXS-1553) GaleraMon ignores server's SSL configuration
+* [MXS-1540](https://jira.mariadb.org/browse/MXS-1540) Race conditions in Galeramon server parameter handling
+* [MXS-1536](https://jira.mariadb.org/browse/MXS-1536) Fatal: MaxScale 2.1.10 received fatal signal 11. Attempting backtrace. Commit ID: 96c3f0dda3b5a9640c4995f46ac8efec77686269 System name: Linux Release string: NAME=CentOS Linux
+* [MXS-1529](https://jira.mariadb.org/browse/MXS-1529) OOM: mxs_realloc can be repeated this way
+* [MXS-1509](https://jira.mariadb.org/browse/MXS-1509) Show correct server state for multisource replication
+
## Packaging
RPM and Debian packages are provided for the Linux distributions supported by
diff --git a/Documentation/Routers/Binlogrouter.md b/Documentation/Routers/Binlogrouter.md
index b54acb9fa..cc6ac9d78 100644
--- a/Documentation/Routers/Binlogrouter.md
+++ b/Documentation/Routers/Binlogrouter.md
@@ -362,7 +362,6 @@ follows.
[Replication]
type=service
router=binlogrouter
-servers=masterdb
user=maxscale
passwd=maxpwd
server_id=3
diff --git a/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md b/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md
index f89c716b1..5aaef9552 100644
--- a/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md
+++ b/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md
@@ -372,6 +372,11 @@ Examples with SSL options:
Binlog Router Plugin is compatible with MariaDB 5.5, 10.0, 10.1 and 10.2 as well
as MySQL 5.6 and 5.7.
+Note: When using MariaDB 10.2 or MySQL 5.7 the `send_slave_heartbeat` option
+must be set to On as the slave servers request the hearbeat to MaxScale.
+As an alternative use `CHANGE MASTER TO MASTER_HEARTBEAT_PERIOD=0` in
+the slave server in order to disable the heartbeat request.
+
## Enabling MariaDB 10 compatibility
MariaDB 10 has different slave registration phase so an extra option is required:
@@ -396,6 +401,11 @@ with MySQL 5.7 slaves the `send_slave_heartbeat` option must be set to on.
Binlog Router currently does not work for MySQL 5.5 due to missing
*@@global.binlog_checksum* variable.
+## MariaDB Limitations
+Starting from version 10.2 there are new replication events related
+to binlog event compression: these new events are not supported yet.
+Be sure that `log_bin_compress` is not set in any MariaDB 10.2 server.
+
# MariaDB MaxScale Replication Diagnostics
The binlog router module of MariaDB MaxScale produces diagnostic output that can
diff --git a/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md b/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md
index 91ae243db..5c1955a8b 100644
--- a/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md
+++ b/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md
@@ -7,6 +7,7 @@ For more information about MariaDB MaxScale 2.1, please refer to the
[ChangeLog](../Changelog.md).
For a complete list of changes in MaxScale 2.1, refer to the
+* [MaxScale 2.1.12 Release Notes](../Release-Notes/MaxScale-2.1.12-Release-Notes.md)
* [MaxScale 2.1.11 Release Notes](../Release-Notes/MaxScale-2.1.11-Release-Notes.md)
* [MaxScale 2.1.10 Release Notes](../Release-Notes/MaxScale-2.1.10-Release-Notes.md)
* [MaxScale 2.1.9 Release Notes](../Release-Notes/MaxScale-2.1.9-Release-Notes.md).
diff --git a/VERSION21.cmake b/VERSION21.cmake
index 42ce2974e..e1964f9f7 100644
--- a/VERSION21.cmake
+++ b/VERSION21.cmake
@@ -5,7 +5,7 @@
set(MAXSCALE_VERSION_MAJOR "2" CACHE STRING "Major version")
set(MAXSCALE_VERSION_MINOR "1" CACHE STRING "Minor version")
-set(MAXSCALE_VERSION_PATCH "11" CACHE STRING "Patch version")
+set(MAXSCALE_VERSION_PATCH "12" CACHE STRING "Patch version")
# This should only be incremented if a package is rebuilt
set(MAXSCALE_BUILD_NUMBER 1 CACHE STRING "Release number")
diff --git a/include/maxscale/server.h b/include/maxscale/server.h
index 19f86c126..e1c82b42c 100644
--- a/include/maxscale/server.h
+++ b/include/maxscale/server.h
@@ -138,6 +138,10 @@ typedef struct server
int last_event; /**< The last event that occurred on this server */
bool active_event; /**< Event observed when MaxScale was active */
int64_t triggered_at; /**< Time when the last event was triggered */
+ struct
+ {
+ bool ssl_not_enabled; /**< SSL not used for an SSL enabled server */
+ } log_warning; /**< Whether a specific warning was logged */
#if defined(SS_DEBUG)
skygw_chk_t server_chk_tail;
#endif
@@ -234,6 +238,10 @@ enum
(((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \
(SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE))
+#define SERVER_IS_SLAVE_OF_EXTERNAL_MASTER(s) (((s)->status & \
+ (SERVER_RUNNING|SERVER_SLAVE_OF_EXTERNAL_MASTER)) == \
+ (SERVER_RUNNING|SERVER_SLAVE_OF_EXTERNAL_MASTER))
+
/**
* @brief Allocate a new server
*
diff --git a/maxscale-system-test/bulk_insert.cpp b/maxscale-system-test/bulk_insert.cpp
index 2b00333db..fe83132da 100644
--- a/maxscale-system-test/bulk_insert.cpp
+++ b/maxscale-system-test/bulk_insert.cpp
@@ -205,7 +205,6 @@ int main(int argc, char** argv)
test.maxscales->connect_maxscale(0);
test.repl->connect();
-
test.tprintf("Testing column-wise binding with a direct connection");
test.add_result(bind_by_column(test.repl->nodes[0]), "Bulk inserts with a direct connection should work");
test.tprintf("Testing column-wise binding with readwritesplit");
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs1509 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1509
new file mode 100644
index 000000000..c839b0dad
--- /dev/null
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1509
@@ -0,0 +1,45 @@
+[maxscale]
+threads=###threads###
+
+[MySQL Monitor]
+type=monitor
+module=mysqlmon
+servers=server1,server2
+user=maxskysql
+passwd=skysql
+monitor_interval=1000
+
+[RW Split Router]
+type=service
+router=readwritesplit
+servers=server1,server2
+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
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog
index c85f33b95..81985d120 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog
@@ -7,9 +7,9 @@ type=service
router=binlogrouter
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1
[Binlog Listener]
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in
index c85f33b95..81985d120 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in
@@ -7,9 +7,9 @@ type=service
router=binlogrouter
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1
[Binlog Listener]
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1
index 473832a71..ee369a8f3 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1
@@ -7,9 +7,9 @@ type=service
router=binlogrouter
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2
index 2d41e8396..0d03663a3 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2
@@ -8,9 +8,9 @@ type=service
router=binlogrouter
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,transaction_safety=1,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,transaction_safety=1,mariadb10-compatibility=1
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync
index 4fb918415..76a8f6910 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync
@@ -8,9 +8,9 @@ router=binlogrouter
#servers=master
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=1,transaction_safety=1,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=1,transaction_safety=1,mariadb10-compatibility=1
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0
index 13a459c20..4f55f4ec9 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0
@@ -8,9 +8,9 @@ router=binlogrouter
#servers=master
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=0,transaction_safety=0,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=0,transaction_safety=0,mariadb10-compatibility=1
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1
index 93098385d..8c0267ddc 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1
@@ -8,9 +8,9 @@ router=binlogrouter
#servers=master
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=1,transaction_safety=0,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=1,transaction_safety=0,mariadb10-compatibility=1
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0
index 8e78d9126..20f30f984 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0
@@ -8,9 +8,9 @@ router=binlogrouter
#servers=master
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=0,transaction_safety=1,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=0,transaction_safety=1,mariadb10-compatibility=1
diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe
index 33957920a..6a1078fc9 100755
--- a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe
+++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe
@@ -8,9 +8,9 @@ router=binlogrouter
#servers=master
user=skysql
passwd=skysql
-version_string=5.6.15-log
+#version_string=5.6.15-log
#router_options=server-id=3,user=repl,password=repl,master-id=1
-router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,transaction_safety=1,mariadb10-compatibility=1
+router_options=server-id=9993,send_slave_heartbeat=On,user=repl,password=repl,longburst=500,heartbeat=10,binlogdir=/var/lib/maxscale/Binlog_Service,transaction_safety=1,mariadb10-compatibility=1
diff --git a/maxscale-system-test/crash_out_of_files.cpp b/maxscale-system-test/crash_out_of_files.cpp
index 88a175946..ea214d165 100644
--- a/maxscale-system-test/crash_out_of_files.cpp
+++ b/maxscale-system-test/crash_out_of_files.cpp
@@ -44,7 +44,7 @@ int main(int argc, char *argv[])
Test->repl->close_connections();
Test->stop_timeout();
- Test->check_log_err(0, (char *) "refresh rate limit exceeded", false);
+ Test->check_log_err(0, (char *) "Refresh rate limit exceeded", false);
Test->check_maxscale_alive(0);
int rval = Test->global_result;
delete Test;
diff --git a/maxscale-system-test/mariadb_nodes.cpp b/maxscale-system-test/mariadb_nodes.cpp
index 44b61040d..79718de32 100644
--- a/maxscale-system-test/mariadb_nodes.cpp
+++ b/maxscale-system-test/mariadb_nodes.cpp
@@ -293,10 +293,9 @@ int Mariadb_nodes::stop_nodes()
{
printf("Stopping node %d\n", i);
fflush(stdout);
- local_result += execute_query(nodes[i], (char *) "stop slave;");
- fflush(stdout);
+ local_result += execute_query(nodes[i], "stop slave;");
local_result += stop_node(i);
- fflush(stdout);
+ local_result += ssh_node_f(i, true, "rm -f /var/lib/mysql/*master*.info");
}
return local_result;
}
@@ -345,11 +344,15 @@ int Mariadb_nodes::start_replication()
// Start all nodes
for (int i = 0; i < N; i++)
{
- local_result += start_node(i, (char *) "");
- sprintf(str,
- "mysql -u root %s -e \"STOP SLAVE; RESET SLAVE; RESET SLAVE ALL; RESET MASTER; SET GLOBAL read_only=OFF;\"",
- socket_cmd[i]);
- ssh_node(i, str, true);
+ local_result += start_node(i, (char*)"");
+ ssh_node_f(i, true,
+ "mysql --force -u root %s -e \"STOP SLAVE; STOP ALL SLAVES; RESET SLAVE; RESET SLAVE ALL; RESET MASTER; SET GLOBAL read_only=OFF;\"",
+ socket_cmd[i]);
+ ssh_node_f(i, true, "sudo rm -f /etc/my.cnf.d/kerb.cnf");
+ ssh_node_f(i, true,
+ "for i in `mysql -ss --force -u root %s -e \"SHOW DATABASES\"|grep -iv 'mysql\\|information_schema\\|performance_schema'`; "
+ "do mysql --force -u root %s -e \"DROP DATABASE $i\";"
+ "done", socket_cmd[i], socket_cmd[i]);
}
sprintf(str, "%s/create_user.sh", test_dir);
@@ -361,10 +364,9 @@ int Mariadb_nodes::start_replication()
ssh_node(0, str, false);
// Create a database dump from the master and distribute it to the slaves
- sprintf(str,
- "mysqldump --all-databases --add-drop-database --flush-privileges --master-data=1 --gtid %s > /tmp/master_backup.sql",
- socket_cmd[0]);
- ssh_node(0, str, true);
+ ssh_node_f(0, true, "mysql --force -u root %s -e \"CREATE DATABASE test\"; "
+ "mysqldump --all-databases --add-drop-database --flush-privileges --master-data=1 --gtid %s > /tmp/master_backup.sql",
+ socket_cmd[0], socket_cmd[0]);
sprintf(str, "%s/master_backup.sql", test_dir);
copy_from_node_legacy("/tmp/master_backup.sql", str, 0);
@@ -374,16 +376,11 @@ int Mariadb_nodes::start_replication()
printf("Starting node %d\n", i);
fflush(stdout);
copy_to_node_legacy(str, "/tmp/master_backup.sql", i);
- sprintf(dtr,
- "mysql -u root %s < /tmp/master_backup.sql",
- socket_cmd[i]);
- ssh_node(i, dtr, true);
- char query[512];
-
- sprintf(query, "mysql -u root %s -e \"CHANGE MASTER TO MASTER_HOST=\\\"%s\\\", MASTER_PORT=%d, "
- "MASTER_USER=\\\"repl\\\", MASTER_PASSWORD=\\\"repl\\\";"
- "START SLAVE;\"", socket_cmd[i], IP_private[0], port[0]);
- ssh_node(i, query, true);
+ ssh_node_f(i, true, "mysql --force -u root %s < /tmp/master_backup.sql",
+ socket_cmd[i]);
+ ssh_node_f(i, true, "mysql --force -u root %s -e \"CHANGE MASTER TO MASTER_HOST=\\\"%s\\\", MASTER_PORT=%d, "
+ "MASTER_USER=\\\"repl\\\", MASTER_PASSWORD=\\\"repl\\\";"
+ "START SLAVE;\"", socket_cmd[i], IP_private[0], port[0]);
}
return local_result;
@@ -405,7 +402,7 @@ int Galera_nodes::start_galera()
ssh_node(0, "echo [mysqld] > cluster_address.cnf", false);
ssh_node(0, "echo wsrep_cluster_address=gcomm:// >> cluster_address.cnf", false);
ssh_node(0, "cp cluster_address.cnf /etc/my.cnf.d/", true);
- local_result += start_node(0, (char *) " --wsrep-cluster-address=gcomm://");
+ local_result += start_node(0, (char*)" --wsrep-cluster-address=gcomm://");
sprintf(str, "%s/create_user_galera.sh", test_dir);
copy_to_node_legacy(str, "~/", 0);
@@ -614,6 +611,28 @@ static bool bad_slave_thread_status(MYSQL *conn, const char *field, int node)
return rval;
}
+static bool multi_source_replication(MYSQL *conn, int node)
+{
+ bool rval = true;
+ MYSQL_RES *res;
+
+ if (mysql_query(conn, "SHOW ALL SLAVES STATUS") == 0 &&
+ (res = mysql_store_result(conn)))
+ {
+ if (mysql_num_rows(res) == 1)
+ {
+ rval = false;
+ }
+ else
+ {
+ printf("Node %d: More than one configured slave\n", node);
+ fflush(stdout);
+ }
+ }
+
+ return rval;
+}
+
int Mariadb_nodes::check_replication()
{
int master = 0;
@@ -643,7 +662,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))
+ bad_slave_thread_status(nodes[i], "Slave_SQL_Running", i) ||
+ multi_source_replication(nodes[i], i))
{
res = 1;
}
@@ -1050,7 +1070,7 @@ static void wait_until_pos(MYSQL *mysql, int filenum, int pos)
{
MYSQL_ROW row = mysql_fetch_row(res);
- if (row && row[6] && row[21])
+ if (row && row[5] && strchr(row[5], '.') && row[21])
{
char *file_suffix = strchr(row[5], '.') + 1;
slave_filenum = atoi(file_suffix);
diff --git a/maxscale-system-test/mdbci/add_core_cnf.sh b/maxscale-system-test/mdbci/add_core_cnf.sh
new file mode 100755
index 000000000..aa5db1828
--- /dev/null
+++ b/maxscale-system-test/mdbci/add_core_cnf.sh
@@ -0,0 +1,23 @@
+set -x
+chmod 777 /tmp/
+echo 2 > /proc/sys/fs/suid_dumpable
+sed -i "s/start() {/start() { \n export DAEMON_COREFILE_LIMIT='unlimited'; ulimit -c unlimited; /" /etc/init.d/maxscale
+sed -i "s/log_daemon_msg \"Starting MaxScale\"/export DAEMON_COREFILE_LIMIT='unlimited'; ulimit -c unlimited; log_daemon_msg \"Starting MaxScale\" /" /etc/init.d/maxscale
+echo /tmp/core-%e-%s-%u-%g-%p-%t > /proc/sys/kernel/core_pattern
+
+echo "kernel.core_pattern = /tmp/core-%e-sig%s-user%u-group%g-pid%p-time%t" >> /etc/sysctl.d/core.conf
+echo "kernel.core_uses_pid = 1" >> /etc/sysctl.d/core.conf
+echo "fs.suid_dumpable = 2" >> /etc/sysctl.d/core.conf
+
+echo "DefaultLimitCORE=infinity" >> /etc/systemd/system.conf
+
+echo "* hard core unlimited" >> /etc/security/limits.d/core.conf
+echo "* soft core unlimited" >> /etc/security/limits.d/core.conf
+echo "* soft nofile 65536" >> /etc/security/limits.d/core.conf
+echo "* hard nofile 65536" >> /etc/security/limits.d/core.conf
+
+
+echo "fs.file-max = 65536" >> /etc/sysctl.conf
+
+systemctl daemon-reexec
+sysctl -p
diff --git a/maxscale-system-test/mdbci/backend/create_repl_user.sql b/maxscale-system-test/mdbci/backend/create_repl_user.sql
new file mode 100644
index 000000000..98431e94e
--- /dev/null
+++ b/maxscale-system-test/mdbci/backend/create_repl_user.sql
@@ -0,0 +1,4 @@
+#create user repl@'%' identified by 'repl';
+grant replication slave on *.* to repl@'%' identified by 'repl';
+
+FLUSH PRIVILEGES;
diff --git a/maxscale-system-test/mdbci/backend/create_skysql_user.sql b/maxscale-system-test/mdbci/backend/create_skysql_user.sql
new file mode 100644
index 000000000..668190d4f
--- /dev/null
+++ b/maxscale-system-test/mdbci/backend/create_skysql_user.sql
@@ -0,0 +1,18 @@
+create user skysql@'%' identified by 'skysql';
+create user skysql@'localhost' identified by 'skysql';
+GRANT ALL PRIVILEGES ON *.* TO skysql@'%' WITH GRANT OPTION;
+GRANT ALL PRIVILEGES ON *.* TO skysql@'localhost' WITH GRANT OPTION;
+
+create user maxuser@'%' identified by 'maxpwd';
+create user maxuser@'localhost' identified by 'maxpwd';
+GRANT ALL PRIVILEGES ON *.* TO maxuser@'%' WITH GRANT OPTION;
+GRANT ALL PRIVILEGES ON *.* TO maxuser@'localhost' WITH GRANT OPTION;
+
+create user maxskysql@'%' identified by 'skysql';
+create user maxskysql@'localhost' identified by 'skysql';
+GRANT ALL PRIVILEGES ON *.* TO maxskysql@'%' WITH GRANT OPTION;
+GRANT ALL PRIVILEGES ON *.* TO maxskysql@'localhost' WITH GRANT OPTION;
+
+
+FLUSH PRIVILEGES;
+CREATE DATABASE IF NOT EXISTS test;
diff --git a/maxscale-system-test/mdbci/backend/galera/setup_galera.sh b/maxscale-system-test/mdbci/backend/galera/setup_galera.sh
new file mode 100755
index 000000000..c7e2e182d
--- /dev/null
+++ b/maxscale-system-test/mdbci/backend/galera/setup_galera.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+N=$galera_N
+
+x=`expr $N - 1`
+for i in $(seq 0 $x)
+do
+ num=`printf "%03d" $i`
+ sshkey_var=galera_"$num"_keyfile
+ user_var=galera_"$num"_whoami
+ IP_var=galera_"$num"_network
+
+ sshkey=${!sshkey_var}
+ user=${!user_var}
+ IP=${!IP_var}
+
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo mysql_install_db; sudo chown -R mysql:mysql /var/lib/mysql"
+done
diff --git a/maxscale-system-test/mdbci/backend/setup_repl.sh b/maxscale-system-test/mdbci/backend/setup_repl.sh
new file mode 100755
index 000000000..ffa2f2251
--- /dev/null
+++ b/maxscale-system-test/mdbci/backend/setup_repl.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+set -x
+
+x=`expr $node_N - 1`
+for i in $(seq 0 $x)
+do
+ num=`printf "%03d" $i`
+ sshkey_var=node_"$num"_keyfile
+ user_var=node_"$num"_whoami
+ IP_var=node_"$num"_network
+ start_cmd_var=node_"$num"_start_db_command
+ stop_cmd_var=node_"$num"_stop_db_command
+
+ sshkey=${!sshkey_var}
+ user=${!user_var}
+ IP=${!IP_var}
+ start_cmd=${!start_cmd_var}
+ stop_cmd=${!stop_cmd_var}
+
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo $stop_cmd"
+ sleep 5
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP 'sudo sed -i "s/bind-address/#bind-address/g" /etc/mysql/my.cnf'
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP 'sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/usr.sbin.mysqld; sudo service apparmor restart'
+
+ mysql_version=`ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP 'mysql --version'`
+ echo $mysql_version | grep "5\."
+ if [ $? == 0 ] ; then
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo sed -i \"s/binlog_row_image=full//\" /etc/my.cnf.d/*.cnf"
+ fi
+
+ echo $mysql_version | grep "5\.7"
+ if [ $? == 0 ] ; then
+# ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo sed -i \"s/## x001/validate-password=OFF/\" /etc/my.cnf.d/*.cnf"
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo mysqld --initialize; sudo chown -R mysql:mysql /var/lib/mysql"
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo $start_cmd"
+
+ mysql_root_password=`ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo cat /var/log/mysqld.log | grep \"temporary password\" | sed -n -e 's/^.*: //p'"`
+
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo mysqladmin -uroot -p'$mysql_root_password' password '$mysql_root_password'"
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "echo \"UNINSTALL PLUGIN validate_password\" | sudo mysql -uroot -p'$mysql_root_password' "
+
+
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo $stop_cmd"
+# ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo sed -i \"s/## x001/validate-password=OFF/\" /etc/my.cnf.d/*.cnf"
+
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo $start_cmd"
+
+# mysql_root_password=`ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo cat /var/log/mysqld.log | grep \"temporary password\" | sed -n -e 's/^.*: //p'"`
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "echo \"show plugins\" | sudo mysql -uroot -p'$mysql_root_password' "
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo mysqladmin -uroot -p'$mysql_root_password' password ''"
+# ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo $start_cmd"
+ else
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo mysql_install_db; sudo chown -R mysql:mysql /var/lib/mysql"
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo $start_cmd"
+ fi
+ sleep 15
+ scp -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ${script_dir}/create_*_user.sql $user@$IP://home/$user/
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo mysql < /home/$user/create_repl_user.sql"
+ ssh -i $sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $user@$IP "sudo mysql < /home/$user/create_skysql_user.sql"
+done
diff --git a/maxscale-system-test/mdbci/cnf/galera_server1.cnf b/maxscale-system-test/mdbci/cnf/galera_server1.cnf
new file mode 100644
index 000000000..03b37157b
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/galera_server1.cnf
@@ -0,0 +1,130 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=1
+wsrep_on=ON
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera000
+
+# INITIAL SETUP
+# Base replication
[:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=1
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/galera_server2.cnf b/maxscale-system-test/mdbci/cnf/galera_server2.cnf
new file mode 100644
index 000000000..79454a443
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/galera_server2.cnf
@@ -0,0 +1,130 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=2
+wsrep_on=ON
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera001
+
+# INITIAL SETUP
+# Base replication [:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=1
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/galera_server3.cnf b/maxscale-system-test/mdbci/cnf/galera_server3.cnf
new file mode 100644
index 000000000..9f77a5687
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/galera_server3.cnf
@@ -0,0 +1,130 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=3
+wsrep_on=ON
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera002
+
+# INITIAL SETUP
+# Base replication [:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=1
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/galera_server4.cnf b/maxscale-system-test/mdbci/cnf/galera_server4.cnf
new file mode 100644
index 000000000..a589a690e
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/galera_server4.cnf
@@ -0,0 +1,130 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=4
+wsrep_on=ON
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera003
+
+# INITIAL SETUP
+# Base replication [:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=1
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/galera_server1.cnf b/maxscale-system-test/mdbci/cnf/mysql56/galera_server1.cnf
new file mode 100644
index 000000000..0e646cb3b
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/galera_server1.cnf
@@ -0,0 +1,135 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=1
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera000
+
+# INITIAL SETUP
+# Base replication [:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Maximum number of rows in write set
+wsrep_max_ws_rows=131072
+
+# Maximum size of write set
+wsrep_max_ws_size=1073741824
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=0
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/galera_server2.cnf b/maxscale-system-test/mdbci/cnf/mysql56/galera_server2.cnf
new file mode 100644
index 000000000..c459d80ad
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/galera_server2.cnf
@@ -0,0 +1,135 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=2
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera001
+
+# INITIAL SETUP
+# Base replication [:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Maximum number of rows in write set
+wsrep_max_ws_rows=131072
+
+# Maximum size of write set
+wsrep_max_ws_size=1073741824
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=0
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/galera_server3.cnf b/maxscale-system-test/mdbci/cnf/mysql56/galera_server3.cnf
new file mode 100644
index 000000000..0bdaafd42
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/galera_server3.cnf
@@ -0,0 +1,135 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=3
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera002
+
+# INITIAL SETUP
+# Base replication [:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Maximum number of rows in write set
+wsrep_max_ws_rows=131072
+
+# Maximum size of write set
+wsrep_max_ws_size=1073741824
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=0
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/galera_server4.cnf b/maxscale-system-test/mdbci/cnf/mysql56/galera_server4.cnf
new file mode 100644
index 000000000..78d510da9
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/galera_server4.cnf
@@ -0,0 +1,135 @@
+[mysqld]
+expire_logs_days=7
+user=mysql
+server_id=4
+
+# Row binary log format is required by Galera
+binlog_format=ROW
+
+log-bin
+
+# InnoDB is currently the only storage engine supported by Galera
+default-storage-engine=innodb
+innodb_file_per_table
+
+# To avoid issues with 'bulk mode inserts' using autoincrement fields
+innodb_autoinc_lock_mode=2
+
+# Required to prevent deadlocks on parallel transaction execution
+innodb_locks_unsafe_for_binlog=1
+
+# Query Cache is not supported by Galera wsrep replication
+query_cache_size=0
+query_cache_type=0
+
+# INITIAL SETUP
+# In some systems bind-address defaults to 127.0.0.1, and with mysqldump SST
+# it will have (most likely) disastrous consequences on donor node
+bind-address=###NODE-ADDRESS###
+
+##
+## WSREP options
+##
+
+# INITIAL SETUP
+# For the initial setup, wsrep should be disabled
+wsrep_provider=none
+# After initial setup, parameter should have full path to wsrep provider library
+wsrep_provider=###GALERA-LIB-PATH###
+
+# Provider specific configuration options
+wsrep_provider_options = "evs.keepalive_period = PT3S; evs.inactive_check_period = PT10S; evs.suspect_timeout = PT30S; evs.inactive_timeout = PT1M; evs.install_timeout = PT1M"
+
+# Logical cluster name. Should be the same for all nodes in the same cluster.
+wsrep_cluster_name=skycluster
+
+# INITIAL SETUP
+# Group communication system handle: for the first node to be launched, the value should be "gcomm://", indicating creation of a new cluster;
+# for the other nodes joining the cluster, the value should be "gcomm://xxx.xxx.xxx.xxx:4567", where xxx.xxx.xxx.xxx should be the ip of a node
+# already on the cluster (usually the first one)
+# DEPRECATED
+# wsrep_cluster_address=gcomm://
+
+# Human-readable node name (non-unique). Hostname by default.
+#wsrep_node_name=###NODE-NAME###
+wsrep_node_name=galera003
+
+# INITIAL SETUP
+# Base replication [:port] of the node.
+# The values supplied will be used as defaults for state transfer receiving,
+# listening ports and so on. Default: address of the first network interface.
+wsrep_node_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# Address for incoming client connections. Autodetect by default.
+wsrep_node_incoming_address=###NODE-ADDRESS###
+
+# Number of threads that will process writesets from other nodes
+wsrep_slave_threads=1
+
+# Generate fake primary keys for non-PK tables (required for multi-master
+# and parallel applying operation)
+wsrep_certify_nonPK=1
+
+# Maximum number of rows in write set
+wsrep_max_ws_rows=131072
+
+# Maximum size of write set
+wsrep_max_ws_size=1073741824
+
+# Debug level logging (1 = enabled)
+wsrep_debug=1
+
+# Convert locking sessions into transactions
+wsrep_convert_LOCK_to_trx=0
+
+# Number of retries for deadlocked autocommits
+wsrep_retry_autocommit=1
+
+# Change auto_increment_increment and auto_increment_offset automatically
+wsrep_auto_increment_control=1
+
+# Retry autoinc insert, when the insert failed for "duplicate key error"
+wsrep_drupal_282555_workaround=0
+
+# Enable "strictly synchronous" semantics for read operations
+wsrep_causal_reads=0
+
+# Command to call when node status or cluster membership changes.
+# Will be passed all or some of the following options:
+# --status - new status of this node
+# --uuid - UUID of the cluster
+# --primary - whether the component is primary or not ("yes"/"no")
+# --members - comma-separated list of members
+# --index - index of this node in the list
+wsrep_notify_cmd=
+
+##
+## WSREP State Transfer options
+##
+
+# State Snapshot Transfer method
+#wsrep_sst_method=mysqldump
+#wsrep_sst_method=xtrabackup
+wsrep_sst_method=rsync
+
+# INITIAL SETUP
+# Address which donor should send State Snapshot to.
+# Should be the address of the CURRENT node. DON'T SET IT TO DONOR ADDRESS!!!
+# (SST method dependent. Defaults to the first IP of the first interface)
+wsrep_sst_receive_address=###NODE-ADDRESS###
+
+# INITIAL SETUP
+# SST authentication string. This will be used to send SST to joining nodes.
+# Depends on SST method. For mysqldump method it is root:
+#wsrep_sst_auth=###REP-USERNAME###:###REP-PASSWORD###
+wsrep_sst_auth=repl:repl
+
+# Desired SST donor name.
+#wsrep_sst_donor=
+
+# Reject client queries when donating SST (false)
+#wsrep_sst_donor_rejects_queries=0
+
+# Protocol version to use
+# wsrep_protocol_version=
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server1.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server1.cnf
new file mode 100644
index 000000000..38d8d2c8a
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server1.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=1
+#slave_parallel_threads=2
+user=mysql
+## x001
+#max_long_data_size=1000000000
+#innodb_log_file_size=2000000000
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server10.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server10.cnf
new file mode 100644
index 000000000..7c704b7de
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server10.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=10
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server11.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server11.cnf
new file mode 100644
index 000000000..815a54f4d
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server11.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=11
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server12.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server12.cnf
new file mode 100644
index 000000000..e3af6c8f8
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server12.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=12
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server13.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server13.cnf
new file mode 100644
index 000000000..67dade68a
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server13.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=13
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server14.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server14.cnf
new file mode 100644
index 000000000..dd237e89d
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server14.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=14
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server15.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server15.cnf
new file mode 100644
index 000000000..944cfcf50
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server15.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=15
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server2.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server2.cnf
new file mode 100644
index 000000000..321ebf9c4
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server2.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=2
+#slave_parallel_threads=2
+user=mysql
+## x001
+#max_long_data_size=1000000000
+#innodb_log_file_size=2000000000
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server3.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server3.cnf
new file mode 100644
index 000000000..0d3694f79
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server3.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=3
+#slave_parallel_threads=2
+user=mysql
+## x001
+#max_long_data_size=1000000000
+#innodb_log_file_size=2000000000
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server4.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server4.cnf
new file mode 100644
index 000000000..2cf00b9bb
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server4.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=4
+#slave_parallel_threads=2
+user=mysql
+## x001
+#max_long_data_size=1000000000
+#innodb_log_file_size=2000000000
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server5.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server5.cnf
new file mode 100644
index 000000000..9dfd4e786
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server5.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=5
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server6.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server6.cnf
new file mode 100644
index 000000000..5007ec9f8
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server6.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=6
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server7.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server7.cnf
new file mode 100644
index 000000000..de3f8d470
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server7.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=7
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server8.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server8.cnf
new file mode 100644
index 000000000..5ab8c1f83
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server8.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=8
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/mysql56/server9.cnf b/maxscale-system-test/mdbci/cnf/mysql56/server9.cnf
new file mode 100644
index 000000000..0cc2cf435
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/mysql56/server9.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+#log-basename=mar
+log-bin=mar-bin
+#binlog-format=row
+binlog-format=STATEMENT
+server_id=9
+#slave_parallel_threads=2
+user=mysql
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server1.cnf b/maxscale-system-test/mdbci/cnf/server1.cnf
new file mode 100644
index 000000000..3c3367b8f
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server1.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=row
+binlog_row_image=full
+server_id=1
+user=mysql
+## x001
+max_long_data_size=1000000000
+innodb_log_file_size=2000000000
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server10.cnf b/maxscale-system-test/mdbci/cnf/server10.cnf
new file mode 100644
index 000000000..9d7e48b92
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server10.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=10
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server11.cnf b/maxscale-system-test/mdbci/cnf/server11.cnf
new file mode 100644
index 000000000..8733cfede
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server11.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=11
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server12.cnf b/maxscale-system-test/mdbci/cnf/server12.cnf
new file mode 100644
index 000000000..d01d176cf
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server12.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=12
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server13.cnf b/maxscale-system-test/mdbci/cnf/server13.cnf
new file mode 100644
index 000000000..27ccd9ed5
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server13.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=13
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server14.cnf b/maxscale-system-test/mdbci/cnf/server14.cnf
new file mode 100644
index 000000000..e5d2fc2b0
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server14.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=14
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server15.cnf b/maxscale-system-test/mdbci/cnf/server15.cnf
new file mode 100644
index 000000000..776376b2b
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server15.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=15
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server2.cnf b/maxscale-system-test/mdbci/cnf/server2.cnf
new file mode 100644
index 000000000..ae6b3df84
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server2.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=row
+binlog_row_image=full
+server_id=2
+user=mysql
+## x001
+max_long_data_size=1000000000
+innodb_log_file_size=2000000000
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server3.cnf b/maxscale-system-test/mdbci/cnf/server3.cnf
new file mode 100644
index 000000000..24b0eaee4
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server3.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=row
+binlog_row_image=full
+server_id=3
+user=mysql
+## x001
+max_long_data_size=1000000000
+innodb_log_file_size=2000000000
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server4.cnf b/maxscale-system-test/mdbci/cnf/server4.cnf
new file mode 100644
index 000000000..c291692db
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server4.cnf
@@ -0,0 +1,39 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=row
+binlog_row_image=full
+server_id=4
+user=mysql
+## x001
+max_long_data_size=1000000000
+innodb_log_file_size=2000000000
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server5.cnf b/maxscale-system-test/mdbci/cnf/server5.cnf
new file mode 100644
index 000000000..0abd4dc70
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server5.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=5
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server6.cnf b/maxscale-system-test/mdbci/cnf/server6.cnf
new file mode 100644
index 000000000..5297a2b95
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server6.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=6
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server7.cnf b/maxscale-system-test/mdbci/cnf/server7.cnf
new file mode 100644
index 000000000..6698ca27c
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server7.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=7
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server8.cnf b/maxscale-system-test/mdbci/cnf/server8.cnf
new file mode 100644
index 000000000..a33cf1fb2
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server8.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=8
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/cnf/server9.cnf b/maxscale-system-test/mdbci/cnf/server9.cnf
new file mode 100644
index 000000000..f00a3454c
--- /dev/null
+++ b/maxscale-system-test/mdbci/cnf/server9.cnf
@@ -0,0 +1,36 @@
+
+#
+# These groups are read by MariaDB server.
+# Use it for options that only the server (but not clients) should see
+#
+# See the examples of server my.cnf files in /usr/share/mysql/
+#
+
+# this is read by the standalone daemon and embedded servers
+[server]
+
+# this is only for the mysqld standalone daemon
+[mysqld]
+log-slave-updates
+log-bin=mar-bin
+binlog-format=STATEMENT
+server_id=9
+user=mysql
+## x001
+slave-skip-errors=all
+
+# this is only for embedded server
+[embedded]
+
+# This group is only read by MariaDB-5.5 servers.
+# If you use the same .cnf file for MariaDB of different versions,
+# use this group for options that older servers don't understand
+[mysqld-5.5]
+
+# These two groups are only read by MariaDB servers, not by MySQL.
+# If you use the same .cnf file for MySQL and MariaDB,
+# you can put MariaDB-only options here
+[mariadb]
+
+[mariadb-5.5]
+
diff --git a/maxscale-system-test/mdbci/configure_backend.sh b/maxscale-system-test/mdbci/configure_backend.sh
new file mode 100644
index 000000000..c05f697f8
--- /dev/null
+++ b/maxscale-system-test/mdbci/configure_backend.sh
@@ -0,0 +1,9 @@
+. ${script_dir}/set_env.sh $name
+
+${script_dir}/backend/setup_repl.sh
+${script_dir}/backend/galera/setup_galera.sh
+
+${script_dir}/configure_core.sh
+
+rm ~/vagrant_lock
+
diff --git a/maxscale-system-test/mdbci/configure_core.sh b/maxscale-system-test/mdbci/configure_core.sh
new file mode 100755
index 000000000..2c72b5ac9
--- /dev/null
+++ b/maxscale-system-test/mdbci/configure_core.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -x
+
+ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $maxscale_access_user@$maxscale_IP '$maxscale_access_sudo service iptables stop'
+
+ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $maxscale_access_user@$maxscale_IP "$maxscale_access_sudo mkdir ccore; $maxscale_access_sudo chown $maxscale_access_user ccore"
+
+scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ${script_dir}/add_core_cnf.sh $maxscale_access_user@$maxscale_IP:./ccore/
+ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $maxscale_access_user@$maxscale_IP "$maxscale_access_sudo /home/$maxscale_access_user/ccore/add_core_cnf.sh"
+
+set +x
diff --git a/maxscale-system-test/mdbci/configure_log_dir.sh b/maxscale-system-test/mdbci/configure_log_dir.sh
new file mode 100644
index 000000000..568c298b7
--- /dev/null
+++ b/maxscale-system-test/mdbci/configure_log_dir.sh
@@ -0,0 +1,15 @@
+set -x
+LOGS_DIR=${logs_dir:-$HOME/LOGS}
+echo $JOB_NAME | grep "/"
+if [ $? == 0 ] ; then
+ export job_name_buildID=`echo $JOB_NAME | sed "s|/|-$BUILD_NUMBER/|"`
+ export logs_publish_dir="${LOGS_DIR}/${job_name_buildID}/"
+else
+ export logs_publish_dir="${LOGS_DIR}/${JOB_NAME}-${BUILD_NUMBER}"
+fi
+
+export job_name_buildID=`echo ${JOB_NAME} | sed "s|/|-${BUILD_NUMBER}/|"`
+export logs_publish_dir="${LOGS_DIR}/${job_name_buildID}-${BUILD_NUMBER}"
+echo "Logs go to ${logs_publish_dir}"
+mkdir -p ${logs_publish_dir}
+
diff --git a/maxscale-system-test/mdbci/copy_logs.sh b/maxscale-system-test/mdbci/copy_logs.sh
new file mode 100755
index 000000000..a3a9ba3db
--- /dev/null
+++ b/maxscale-system-test/mdbci/copy_logs.sh
@@ -0,0 +1,5 @@
+set -x
+rsync -a --no-o --no-g LOGS ${logs_publish_dir}
+chmod a+r ${logs_publish_dir}/*
+cp -r ${MDBCI_VM_PATH}/$name ${logs_publish_dir}
+cp ${MDBCI_VM_PATH}/${name}.json ${logs_publish_dir}
diff --git a/maxscale-system-test/mdbci/create_config.sh b/maxscale-system-test/mdbci/create_config.sh
new file mode 100755
index 000000000..ed449330f
--- /dev/null
+++ b/maxscale-system-test/mdbci/create_config.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+set -x
+export dir=`pwd`
+
+# read the name of build scripts directory
+export script_dir="$(dirname $(readlink -f $0))"
+
+. ${script_dir}/set_run_test_variables.sh
+
+${mdbci_dir}/repository-config/generate_all.sh repo.d
+${mdbci_dir}/repository-config/maxscale-ci.sh $target repo.d
+
+
+export repo_dir=$dir/repo.d/
+
+export provider=`${mdbci_dir}/mdbci show provider $box --silent 2> /dev/null`
+export backend_box=${backend_box:-"centos_7_"$provider}
+
+if [ "$product" == "mysql" ] ; then
+ export cnf_path=${script_dir}/cnf/mysql56
+fi
+
+mkdir -p ${MDBCI_VM_PATH}/$name
+cd ${MDBCI_VM_PATH}/$name
+vagrant destroy -f
+cd $dir
+
+mkdir ${MDBCI_VM_PATH}/$name/cnf
+cp -r ${cnf_path}/* ${MDBCI_VM_PATH}/$name/cnf/
+export cnd_path="${MDBCI_VM_PATH}/$name/cnf/"
+
+ eval "cat < /dev/null > ${MDBCI_VM_PATH}/${name}.json
+
+${mdbci_dir}/mdbci --override --template ${MDBCI_VM_PATH}/${name}.json --repo-dir ${repo_dir} generate $name
+
+while [ -f ~/vagrant_lock ]
+do
+ echo "vagrant is locked, waiting ..."
+ sleep 5
+done
+touch ~/vagrant_lock
+echo ${JOB_NAME}-${BUILD_NUMBER} >> ~/vagrant_lock
+
+echo "running vagrant up $provider"
+
+${mdbci_dir}/mdbci up $name --attempts 3
+if [ $? != 0 ]; then
+ echo "Error creating configuration"
+ rm ~/vagrant_lock
+ exit 1
+fi
+
+cp ~/build-scripts/team_keys .
+${mdbci_dir}/mdbci public_keys --key ${team_keys} $name
+
+rm ~/vagrant_lock
+exit 0
diff --git a/maxscale-system-test/mdbci/destroy.sh b/maxscale-system-test/mdbci/destroy.sh
new file mode 100755
index 000000000..d12ec0764
--- /dev/null
+++ b/maxscale-system-test/mdbci/destroy.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+dir=`pwd`
+cd ${MDBCI_VM_PATH}/${name}
+vagrant destroy -f
+cd $dir
+
+rm -rf ${MDBCI_VM_PATH}/${name}
+rm -rf ${MDBCI_VM_PATH}/${name}.json
+rm -rf ${MDBCI_VM_PATH}/${name}_network_config
diff --git a/maxscale-system-test/mdbci/run_test.sh b/maxscale-system-test/mdbci/run_test.sh
new file mode 100755
index 000000000..eb0f32c73
--- /dev/null
+++ b/maxscale-system-test/mdbci/run_test.sh
@@ -0,0 +1,103 @@
+#!/bin/bash
+
+# see set_run_test_variables.sh for default values of all variables
+
+# $box - Name of Vagrant box for Maxscale machine
+# see lists of supported boxes
+# https://github.com/mariadb-corporation/mdbci/tree/integration/BOXES
+
+# $template - name of MDBCI json template file
+# Template file have to be in ./templates/, file name
+# have to be '$template.json.template
+# Template file can contain references to any variables -
+# all ${variable_name} will be replaced with values
+
+# $name - name of test run. It can be any string of leytters or digits
+# If it is not defined, name will be automatically genereted
+# using $box and current date and time
+
+# $ci_url - URL to Maxscale CI repository
+# (default "http://max-tst-01.mariadb.com/ci-repository/")
+# if build is done also locally and binaries are not uploaded to
+# max-tst-01.mariadb.com $ci_url should toint to local web server
+# e.g. http://192.168.122.1/repository (IP should be a host IP in the
+# virtual network (not 127.0.0.1))
+
+# $product - 'mariadb' or 'mysql'
+
+# $version - version of backend DB (e.g. '10.1', '10.2')
+
+# $galera_version - version of Galera backend DB
+# same as $version by default
+
+# $target - name of binary repository
+# (name of subdirectroy http://max-tst-01.mariadb.com/ci-repository/)
+
+# $team_keys - path to the file with open ssh keys to be
+# installed on all VMs (default ${HOME}/team_keys)
+
+# $don_not_destroy_vm - if 'yes' VM won't be destored afther the test
+
+# $test_set - parameters to be send to 'ctest' (e.g. '-I 1,100',
+# '-LE UNSTABLE'
+
+export vm_memory=${vm_memory:-"2048"}
+export dir=`pwd`
+
+ulimit -n
+
+# read the name of build scripts directory
+export script_dir="$(dirname $(readlink -f $0))"
+
+. ${script_dir}/set_run_test_variables.sh
+
+rm -rf LOGS
+
+export target=`echo $target | sed "s/?//g"`
+export name=`echo $name | sed "s/?//g"`
+
+. ${script_dir}/configure_log_dir.sh
+
+cd ${script_dir}/..
+
+cmake . -DBUILDNAME=$name -DCMAKE_BUILD_TYPE=Debug
+make
+
+${script_dir}/create_config.sh
+res=$?
+
+if [ $res == 0 ] ; then
+ . ${script_dir}/configure_backend.sh
+ ${mdbci_dir}/mdbci snapshot take --path-to-nodes $name --snapshot-name clean
+
+ if [ ! -z "${named_test}" ] ; then
+ ./${named_test}
+ else
+ ./check_backend
+ if [ $? != 0 ]; then
+ echo "Backend broken!"
+ if [ "${do_not_destroy_vm}" != "yes" ] ; then
+ ${script_dir}/destroy.sh
+ fi
+ rm ~/vagrant_lock
+ exit 1
+ fi
+ ctest -VV -D Nightly ${test_set}
+ fi
+
+ cd $dir
+ ${script_dir}/copy_logs.sh
+else
+ echo "Failed to create VMs, exiting"
+ if [ "${do_not_destroy_vm}" != "yes" ] ; then
+ ${script_dir}/destroy.sh
+ fi
+ rm ~/vagrant_lock
+ exit 1
+fi
+
+if [ "${do_not_destroy_vm}" != "yes" ] ; then
+ ${script_dir}/destroy.sh
+ echo "clean up done!"
+fi
+
diff --git a/maxscale-system-test/mdbci/run_test_snapshot.sh b/maxscale-system-test/mdbci/run_test_snapshot.sh
new file mode 100755
index 000000000..77d319121
--- /dev/null
+++ b/maxscale-system-test/mdbci/run_test_snapshot.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+
+function checkExitStatus {
+ returnCode=$1
+ errorMessage=$2
+ lockFilePath=$3
+ if [ "$returnCode" != 0 ]; then
+ echo "$errorMesage"
+ rm $lockFilePath
+ echo "Snapshot lock file was deleted due to an error"
+ exit 1
+ fi
+}
+
+set -x
+export dir=`pwd`
+
+# read the name of build scripts directory
+export script_dir="$(dirname $(readlink -f $0))"
+
+. ${script_dir}/set_run_test_variables.sh
+export name="$box-$product-$version-permanent"
+
+export snapshot_name=${snapshot_name:-"clean"}
+
+rm -rf LOGS
+
+export target=`echo $target | sed "s/?//g"`
+export name=`echo $name | sed "s/?//g"`
+
+. ${script_dir}/configure_log_dir.sh
+
+# Setting snapshot_lock
+export snapshot_lock_file=${MDBCI_VM_PATH}/${name}_snapshot_lock
+if [ -f ${snapshot_lock_file} ]; then
+ echo "Snapshot is locked, waiting ..."
+fi
+while [ -f ${snapshot_lock_file} ]
+do
+ sleep 5
+done
+
+touch ${snapshot_lock_file}
+echo $JOB_NAME-$BUILD_NUMBER >> ${snapshot_lock_file}
+
+export repo_dir=$dir/repo.d/
+
+${mdbci_dir}/mdbci snapshot revert --path-to-nodes $name --snapshot-name $snapshot_name
+
+if [ $? != 0 ]; then
+ ${script_dir}/destroy.sh
+ ${MDBCI_VM_PATH}/scripts/clean_vms.sh $name
+
+ ${script_dir}/create_config.sh
+ checkExitStatus $? "Error creating configuration" $snapshot_lock_file
+ . ${script_dir}/configure_backend.sh
+
+ echo "Creating snapshot from new config"
+ $HOME/mdbci/mdbci snapshot take --path-to-nodes $name --snapshot-name $snapshot_name
+fi
+
+. ${script_dir}/set_env.sh "$name"
+
+${mdbci_dir}/repository-config/maxscale-ci.sh $target repo.d
+
+
+${mdbci_dir}/mdbci sudo --command 'yum remove maxscale -y' $name/maxscale
+${mdbci_dir}/mdbci sudo --command 'yum clean all' $name/maxscale
+
+${mdbci_dir}/mdbci setup_repo --product maxscale $name/maxscale --repo-dir $repo_dir
+${mdbci_dir}/mdbci install_product --product maxscale $name/maxscale --repo-dir $repo_dir
+
+checkExitStatus $? "Error installing Maxscale" $snapshot_lock_file
+
+cd ${script_dir}/..
+
+cmake . -DBUILDNAME=$JOB_NAME-$BUILD_NUMBER-$target
+make
+
+./check_backend --restart-galera
+
+checkExitStatus $? "Failed to check backends" $snapshot_lock_file
+
+ctest $test_set -VV -D Nightly
+
+${script_dir}/copy_logs.sh
+
+# Removing snapshot_lock
+rm ${snapshot_lock_file}
+
diff --git a/maxscale-system-test/mdbci/set_env.sh b/maxscale-system-test/mdbci/set_env.sh
new file mode 100644
index 000000000..1fb25a35e
--- /dev/null
+++ b/maxscale-system-test/mdbci/set_env.sh
@@ -0,0 +1,97 @@
+#!/bin/bash
+set -x
+echo $*
+export MDBCI_VM_PATH=${MDBCI_VM_PATH:-$HOME/vms}
+export mdbci_dir=${mdbci_dir:-"$HOME/mdbci/"}
+
+export config_name="$1"
+if [ -z $1 ] ; then
+ config_name="test1"
+fi
+
+export curr_dir=`pwd`
+
+export maxscale_binlog_dir="/var/lib/maxscale/Binlog_Service"
+export maxdir="/usr/bin/"
+export maxdir_bin="/usr/bin/"
+export maxscale_cnf="/etc/maxscale.cnf"
+export maxscale_log_dir="/var/log/maxscale/"
+
+# Number of nodes
+export galera_N=`cat "$MDBCI_VM_PATH/$config_name"_network_config | grep galera | grep network | wc -l`
+export node_N=`cat "$MDBCI_VM_PATH/$config_name"_network_config | grep node | grep network | wc -l`
+sed "s/^/export /g" "$MDBCI_VM_PATH/$config_name"_network_config > "$curr_dir"/"$config_name"_network_config_export
+source "$curr_dir"/"$config_name"_network_config_export
+
+# IP Of MaxScale machine
+export maxscale_IP=$maxscale_network
+export maxscale_sshkey=$maxscale_keyfile
+
+# User name and Password for Master/Slave replication setup (should have all PRIVILEGES)
+export node_user="skysql"
+export node_password="skysql"
+
+# User name and Password for Galera setup (should have all PRIVILEGES)
+export galera_user="skysql"
+export galera_password="skysql"
+
+export maxscale_user="skysql"
+export maxscale_password="skysql"
+
+export maxadmin_password="mariadb"
+
+for prefix in "node" "galera"
+do
+ N_var="$prefix"_N
+ Nx=${!N_var}
+ N=`expr $Nx - 1`
+ for i in $(seq 0 $N)
+ do
+ num=`printf "%03d" $i`
+ eval 'export "$prefix"_"$num"_port=3306'
+ eval 'export "$prefix"_"$num"_access_sudo=sudo'
+
+ start_cmd_var="$prefix"_"$num"_start_db_command
+ stop_cmd_var="$prefix"_"$num"_stop_db_command
+ mysql_exe=`${mdbci_dir}/mdbci ssh --command 'ls /etc/init.d/mysql* 2> /dev/null | tr -cd "[:print:]"' $config_name/node_$num --silent 2> /dev/null`
+ echo $mysql_exe | grep -i "mysql"
+ if [ $? != 0 ] ; then
+ service_name=`${mdbci_dir}/mdbci ssh --command 'systemctl list-unit-files | grep mysql' $config_name/node_$num --silent`
+ echo $service_name | grep mysql
+ if [ $? == 0 ] ; then
+ echo $service_name | grep mysqld
+ if [ $? == 0 ] ; then
+ eval 'export $start_cmd_var="service mysqld start "'
+ eval 'export $stop_cmd_var="service mysqld stop "'
+ else
+ eval 'export $start_cmd_var="service mysql start "'
+ eval 'export $stop_cmd_var="service mysql stop "'
+ fi
+ else
+ ${mdbci_dir}/mdbci ssh --command 'echo \"/usr/sbin/mysqld \$* 2> stderr.log > stdout.log &\" > mysql_start.sh; echo \"sleep 20\" >> mysql_start.sh; echo \"disown\" >> mysql_start.sh; chmod a+x mysql_start.sh' $config_name/node_$num --silent
+ eval 'export $start_cmd_var="/home/$au/mysql_start.sh "'
+ eval 'export $start_cmd_var="killall mysqld "'
+ fi
+ else
+ eval 'export $start_cmd_var="$mysql_exe start "'
+ eval 'export $stop_cmd_var="$mysql_exe stop "'
+ fi
+
+ eval 'export "$prefix"_"$num"_start_vm_command="cd $mdbci_dir/$config_name;vagrant up node_$num --provider=$provider; cd $curr_dir"'
+ eval 'export "$prefix"_"$num"_kill_vm_command="cd $mdbci_dir/$config_name;vagrant halt node_$num --provider=$provider; cd $curr_dir"'
+ done
+done
+
+export maxscale_access_user=$maxscale_whoami
+export maxscale_access_sudo="sudo "
+
+# Sysbench directory (should be sysbench >= 0.5)
+export sysbench_dir=${sysbench_dir:-"$HOME/sysbench_deb7/sysbench/"}
+
+export ssl=true
+
+export take_snapshot_command="${mdbci_dir}/mdbci snapshot take --path-to-nodes $name --snapshot-name "
+export revert_snapshot_command="${mdbci_dir}/mdbci snapshot revert --path-to-nodes $name --snapshot-name "
+#export use_snapshots=yes
+
+set +x
diff --git a/maxscale-system-test/mdbci/set_run_test_variables.sh b/maxscale-system-test/mdbci/set_run_test_variables.sh
new file mode 100644
index 000000000..27bd17382
--- /dev/null
+++ b/maxscale-system-test/mdbci/set_run_test_variables.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+
+export MDBCI_VM_PATH=${MDBCI_VM_PATH:-$HOME/vms}
+mkdir -p $MDBCI_VM_PATH
+echo "MDBCI_VM_PATH=$MDBCI_VM_PATH"
+
+export box=${box:-"centos_7_libvirt"}
+echo "box=$box"
+
+export template=${template:-"default"}
+
+export curr_date=`date '+%Y-%m-%d_%H-%M'`
+
+export name=${name:-$box-${curr_date}}
+
+export mdbci_dir=${mdbci_dir:-"$HOME/mdbci/"}
+export ci_url=${ci_url:-"http://max-tst-01.mariadb.com/ci-repository/"}
+
+export product=${product:-"mariadb"}
+export version=${version:-"10.2"}
+export target=${target:-"develop"}
+export vm_memory=${vm_memory:-"2048"}
+export cnf_path=${script_dir}/cnf
+export JOB_NAME=${JOB_NAME:-"local_test"}
+export BUILD_NUMBER=${BUILD_NUMBER:-`date '+%Y%m%d%H%M'`}
+export BUILD_TAG=${BUILD_TAG:-jenkins-${JOB_NAME}-${BUILD_NUMBER}}
+export team_keys=${team_keys:-${HOME}/team_keys}
+export galera_version=${galera_version:-$version}
+export do_not_destroy_vm=${do_not_destroy_vm:-"no"}
+#export test_set=${test_set:-"-LE UNSTABLE"}
+export test_set=${test_set:-"-I 1,5"}
diff --git a/maxscale-system-test/mdbci/templates/big.json.template b/maxscale-system-test/mdbci/templates/big.json.template
new file mode 100644
index 000000000..9592c5647
--- /dev/null
+++ b/maxscale-system-test/mdbci/templates/big.json.template
@@ -0,0 +1,152 @@
+{
+ "node_000" :
+ {
+ "hostname" : "node000",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server1.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+
+ },
+
+ "node_001" :
+ {
+ "hostname" : "node001",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server2.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "node_002" :
+ {
+ "hostname" : "node002",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server3.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "node_003" :
+ {
+ "hostname" : "node003",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server4.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_004" :
+ {
+ "hostname" : "node004",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server5.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_005" :
+ {
+ "hostname" : "node005",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server6.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_006" :
+ {
+ "hostname" : "node006",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server7.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_007" :
+ {
+ "hostname" : "node007",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server8.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_000" :
+ {
+ "hostname" : "galera000",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server1.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_001" :
+ {
+ "hostname" : "galera001",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server2.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_002" :
+ {
+ "hostname" : "galera002",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server3.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_003" :
+ {
+ "hostname" : "galera003",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server4.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "maxscale" :
+ {
+ "hostname" : "maxscale",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "maxscale"
+ }
+
+ }
+}
diff --git a/maxscale-system-test/mdbci/templates/big15.json.template b/maxscale-system-test/mdbci/templates/big15.json.template
new file mode 100644
index 000000000..5d50432d7
--- /dev/null
+++ b/maxscale-system-test/mdbci/templates/big15.json.template
@@ -0,0 +1,229 @@
+{
+ "node_000" :
+ {
+ "hostname" : "node_000",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server1.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+
+ },
+
+ "node_001" :
+ {
+ "hostname" : "node_001",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server2.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "node_002" :
+ {
+ "hostname" : "node_002",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server3.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "node_003" :
+ {
+ "hostname" : "node_003",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server4.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_004" :
+ {
+ "hostname" : "node_004",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server5.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_005" :
+ {
+ "hostname" : "node_005",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server6.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_006" :
+ {
+ "hostname" : "node_006",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server7.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_007" :
+ {
+ "hostname" : "node_007",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server8.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_008" :
+ {
+ "hostname" : "node_008",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server9.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_009" :
+ {
+ "hostname" : "node_009",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server10.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_0010" :
+ {
+ "hostname" : "node_0010",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server11.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_0011" :
+ {
+ "hostname" : "node_0011",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server12.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_0012" :
+ {
+ "hostname" : "node_0012",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server13.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_0013" :
+ {
+ "hostname" : "node_0013",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server14.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+ "node_0014" :
+ {
+ "hostname" : "node_0014",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "###product###",
+ "version": "###version###",
+ "cnf_template" : "server15.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_000" :
+ {
+ "hostname" : "galera_000",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server1.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_001" :
+ {
+ "hostname" : "galera_001",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server2.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_002" :
+ {
+ "hostname" : "galera_002",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server3.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_003" :
+ {
+ "hostname" : "galera_003",
+ "box" : "centos_7_aws",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server4.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "maxscale" :
+ {
+ "hostname" : "maxscale",
+ "box" : "centos_7_aws_large",
+ "product" : {
+ "name": "maxscale"
+ }
+
+ }
+}
diff --git a/maxscale-system-test/mdbci/templates/default.json.template b/maxscale-system-test/mdbci/templates/default.json.template
new file mode 100644
index 000000000..2376e46a7
--- /dev/null
+++ b/maxscale-system-test/mdbci/templates/default.json.template
@@ -0,0 +1,117 @@
+{
+ "node_000" :
+ {
+ "hostname" : "node000",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "${product}",
+ "version": "${version}",
+ "cnf_template" : "server1.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+
+ },
+
+ "node_001" :
+ {
+ "hostname" : "node001",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "${product}",
+ "version": "${version}",
+ "cnf_template" : "server2.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+ },
+
+ "node_002" :
+ {
+ "hostname" : "node002",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "${product}",
+ "version": "${version}",
+ "cnf_template" : "server3.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+ },
+
+ "node_003" :
+ {
+ "hostname" : "node003",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "${product}",
+ "version": "${version}",
+ "cnf_template" : "server4.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+ },
+
+ "galera_000" :
+ {
+ "hostname" : "galera000",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "galera",
+ "version": "${galera_version}",
+ "cnf_template" : "galera_server1.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+ },
+
+ "galera_001" :
+ {
+ "hostname" : "galera001",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "galera",
+ "version": "${galera_version}",
+ "cnf_template" : "galera_server2.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+ },
+
+ "galera_002" :
+ {
+ "hostname" : "galera002",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "galera",
+ "version": "${galera_version}",
+ "cnf_template" : "galera_server3.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+ },
+
+ "galera_003" :
+ {
+ "hostname" : "galera003",
+ "box" : "${backend_box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "galera",
+ "version": "${galera_version}",
+ "cnf_template" : "galera_server4.cnf",
+ "cnf_template_path": "${cnf_path}"
+ }
+ },
+
+ "maxscale" :
+ {
+ "hostname" : "maxscale",
+ "box" : "${box}",
+ "memory_size" : "${vm_memory}",
+ "product" : {
+ "name": "maxscale"
+ }
+
+ }
+}
diff --git a/maxscale-system-test/mdbci/templates/performance.json.template b/maxscale-system-test/mdbci/templates/performance.json.template
new file mode 100644
index 000000000..a767f9fc2
--- /dev/null
+++ b/maxscale-system-test/mdbci/templates/performance.json.template
@@ -0,0 +1,67 @@
+{
+
+###nodes###
+
+ "galera_000" :
+ {
+ "hostname" : "galera000",
+ "box" : "centos_7_libvirt",
+ "memory_size" : "2048",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server1.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_001" :
+ {
+ "hostname" : "galera001",
+ "box" : "centos_7_libvirt",
+ "memory_size" : "2048",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server2.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_002" :
+ {
+ "hostname" : "galera002",
+ "box" : "centos_7_libvirt",
+ "memory_size" : "2048",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server3.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "galera_003" :
+ {
+ "hostname" : "galera003",
+ "box" : "centos_7_libvirt",
+ "memory_size" : "2048",
+ "product" : {
+ "name": "galera",
+ "version": "###galera_version###",
+ "cnf_template" : "galera_server4.cnf",
+ "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+ }
+ },
+
+ "maxscale" :
+ {
+ "hostname" : "maxscale",
+ "box" : "###box###",
+ "memory_size" : "2048",
+ "product" : {
+ "name": "maxscale"
+ }
+
+ }
+}
diff --git a/maxscale-system-test/mxs1476.cpp b/maxscale-system-test/mxs1476.cpp
index d4770de54..8dc1e367d 100644
--- a/maxscale-system-test/mxs1476.cpp
+++ b/maxscale-system-test/mxs1476.cpp
@@ -19,7 +19,7 @@ void do_test(TestConnections& test, int master, int slave)
test.try_query(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)");
test.tprintf("Start the slave node and perform another insert");
- test.galera->start_node(slave, (char *) "");
+ test.galera->start_node(slave, (char*)"");
sleep(5);
test.try_query(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)");
test.maxscales->close_maxscale_connections(0);
@@ -31,7 +31,7 @@ void do_test(TestConnections& test, int master, int slave)
test.try_query(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)");
test.tprintf("Start the master node and perform another insert (expecting failure)");
- test.galera->start_node(master, (char *) "");
+ test.galera->start_node(master, (char*)"");
sleep(5);
test.add_result(execute_query_silent(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)") == 0,
"Query should fail");
diff --git a/maxscale-system-test/mxs1509.cpp b/maxscale-system-test/mxs1509.cpp
index 4c495e217..4ae7f61bb 100644
--- a/maxscale-system-test/mxs1509.cpp
+++ b/maxscale-system-test/mxs1509.cpp
@@ -6,7 +6,6 @@
#include "testconnections.h"
#include
-#include "maxscales.h"
void change_master(TestConnections& test, int slave, int master, const char* name = NULL)
{
@@ -19,8 +18,7 @@ void change_master(TestConnections& test, int slave, int master, const char* nam
source += "'";
}
- execute_query(test.repl->nodes[slave],
- "STOP ALL SLAVES;CHANGE MASTER %s TO master_host='%s', master_port=3306, "
+ execute_query(test.repl->nodes[slave], "STOP ALL SLAVES;CHANGE MASTER %s TO master_host='%s', master_port=3306, "
"master_user='%s', master_password='%s', master_use_gtid=slave_pos;START ALL SLAVES",
source.c_str(), test.repl->IP[master], test.repl->user_name, test.repl->password, source.c_str());
}
@@ -30,14 +28,14 @@ const char* dump_status(const StringSet& current, const StringSet& expected)
std::stringstream ss;
ss << "Current status: (";
- for (const auto& a : current)
+ for (const auto& a: current)
{
ss << a << ",";
}
ss << ") Expected status: (";
- for (const auto& a : expected)
+ for (const auto& a: expected)
{
ss << a << ",";
}
@@ -51,8 +49,8 @@ const char* dump_status(const StringSet& current, const StringSet& expected)
void check_status(TestConnections& test, const StringSet& expected_master, const StringSet& expected_slave)
{
sleep(2);
- StringSet master = test.maxscales->get_server_status(0, "server1");
- StringSet slave = test.maxscales->get_server_status(0, "server2");
+ StringSet master = test.get_server_status("server1");
+ StringSet slave = test.get_server_status("server2");
test.add_result(master != expected_master, "Master status is not what was expected: %s",
dump_status(master, expected_master));
test.add_result(slave != expected_slave, "Slave status is not what was expected: %s",
diff --git a/maxscale-system-test/mxs431.cpp b/maxscale-system-test/mxs431.cpp
index 9cf26ce77..15e9d00c8 100644
--- a/maxscale-system-test/mxs431.cpp
+++ b/maxscale-system-test/mxs431.cpp
@@ -5,62 +5,52 @@
* - Connect repeatedly to MaxScale with 'testdb' as the default database and execute SELECT 1
*/
-
-#include
#include "testconnections.h"
-using namespace std;
-
int main(int argc, char *argv[])
{
- TestConnections * Test = new TestConnections(argc, argv);
+ TestConnections test(argc, argv);
char str[256];
- int iterations = Test->smoke ? 100 : 500;
- Test->repl->execute_query_all_nodes((char *) "set global max_connections = 600;");
- Test->set_timeout(30);
- Test->repl->stop_slaves();
- Test->set_timeout(30);
- Test->maxscales->restart_maxscale(0);
- Test->set_timeout(30);
- Test->repl->connect();
- Test->stop_timeout();
+ int iterations = 100;
+ test.repl->execute_query_all_nodes((char *) "set global max_connections = 600;");
+ test.set_timeout(200);
+ test.repl->stop_slaves();
+ test.set_timeout(200);
+ test.restart_maxscale();
+ test.set_timeout(200);
+ test.repl->connect();
+ test.stop_timeout();
/** Create a database on each node */
- for (int i = 0; i < Test->repl->N; i++)
+ for (int i = 0; i < test.repl->N; i++)
{
- Test->set_timeout(20);
+ test.set_timeout(20);
sprintf(str, "DROP DATABASE IF EXISTS shard_db%d", i);
- Test->tprintf("%s\n", str);
- execute_query(Test->repl->nodes[i], str);
- Test->set_timeout(20);
+ test.tprintf("%s\n", str);
+ execute_query(test.repl->nodes[i], str);
+ test.set_timeout(20);
sprintf(str, "CREATE DATABASE shard_db%d", i);
- Test->tprintf("%s\n", str);
- execute_query(Test->repl->nodes[i], str);
- Test->stop_timeout();
+ test.tprintf("%s\n", str);
+ execute_query(test.repl->nodes[i], str);
+ test.stop_timeout();
}
- Test->repl->close_connections();
+ test.repl->close_connections();
- for (int j = 0; j < iterations && Test->global_result == 0; j++)
+ for (int j = 0; j < iterations && test.global_result == 0; j++)
{
- for (int i = 0; i < Test->repl->N; i++)
+ for (int i = 0; i < test.repl->N && test.global_result == 0; i++)
{
sprintf(str, "shard_db%d", i);
- Test->set_timeout(15);
- MYSQL *conn = open_conn_db(Test->maxscales->rwsplit_port[0], Test->maxscales->IP[0],
- str, Test->maxscales->user_name,
- Test->maxscales->password, Test->ssl);
- Test->set_timeout(15);
- Test->tprintf("Trying DB %d\n", i);
- if (execute_query(conn, "SELECT 1"))
- {
- Test->add_result(1, "Failed at %d\n", j);
- break;
- }
+ test.set_timeout(30);
+ MYSQL *conn = open_conn_db(test.rwsplit_port, test.maxscale_IP,
+ str, test.maxscale_user,
+ test.maxscale_password, test.ssl);
+ test.set_timeout(30);
+ test.add_result(execute_query(conn, "SELECT 1"), "Trying DB %d failed at %d", i, j);
mysql_close(conn);
}
}
- int rval = Test->global_result;
- delete Test;
- return rval;
+
+ return test.global_result;
}
diff --git a/maxscale-system-test/testconnections.cpp b/maxscale-system-test/testconnections.cpp
index 52d472af7..72ee7fb81 100644
--- a/maxscale-system-test/testconnections.cpp
+++ b/maxscale-system-test/testconnections.cpp
@@ -332,28 +332,44 @@ TestConnections::~TestConnections()
}
}
-void TestConnections::add_result(int result, const char *format, ...)
+void TestConnections::report_result(const char *format, va_list argp)
{
timeval t2;
gettimeofday(&t2, NULL);
double elapsedTime = (t2.tv_sec - start_time.tv_sec);
elapsedTime += (double) (t2.tv_usec - start_time.tv_usec) / 1000000.0;
- if (result != 0)
+ global_result += 1;
+
+ printf("%04f: TEST_FAILED! ", elapsedTime);
+
+ vprintf(format, argp);
+
+ if (format[strlen(format) - 1] != '\n')
{
- global_result += result;
-
- printf("%04f: TEST_FAILED! ", elapsedTime);
+ printf("\n");
+ }
+}
+void TestConnections::add_result(bool result, const char *format, ...)
+{
+ if (result)
+ {
va_list argp;
va_start(argp, format);
- vprintf(format, argp);
+ report_result(format, argp);
va_end(argp);
+ }
+}
- if (format[strlen(format) - 1] != '\n')
- {
- printf("\n");
- }
+void TestConnections::assert(bool result, const char *format, ...)
+{
+ if (!result)
+ {
+ va_list argp;
+ va_start(argp, format);
+ report_result(format, argp);
+ va_end(argp);
}
}
@@ -1695,6 +1711,47 @@ int TestConnections::try_query_all(int m, const char *sql)
try_query(maxscales->conn_slave[m], sql);
}
+StringSet TestConnections::get_server_status(const char* name)
+{
+ std::set rval;
+ int rc;
+ char* res = maxscales->ssh_node_output_f(0, true, &rc, "maxadmin list servers|grep \'%s\'", name);
+ char* pipe = strrchr(res, '|');
+
+ if (res && pipe)
+ {
+ pipe++;
+ char* tok = strtok(pipe, ",");
+
+ while (tok)
+ {
+ char* p = tok;
+ char *end = strchr(tok, '\n');
+ if (!end)
+ end = strchr(tok, '\0');
+
+ // Trim leading whitespace
+ while (p < end && isspace(*p))
+ {
+ p++;
+ }
+
+ // Trim trailing whitespace
+ while (end > tok && isspace(*end))
+ {
+ *end-- = '\0';
+ }
+
+ rval.insert(p);
+ tok = strtok(NULL, ",\n");
+ }
+
+ free(res);
+ }
+
+ return rval;
+}
+
int TestConnections::list_dirs(int m)
{
for (int i = 0; i < repl->N; i++)
diff --git a/maxscale-system-test/testconnections.h b/maxscale-system-test/testconnections.h
index 6ac25658a..a886a3e85 100644
--- a/maxscale-system-test/testconnections.h
+++ b/maxscale-system-test/testconnections.h
@@ -10,6 +10,7 @@
#include
#include
+typedef std::set StringSet;
/**
* @brief Class contains references to Master/Slave and Galera test setups
@@ -223,7 +224,10 @@ public:
* @param result 0 if step PASSED
* @param format ... message to pring if result is not 0
*/
- void add_result(int result, const char *format, ...);
+ void add_result(bool result, const char *format, ...);
+
+ /** Same as add_result() but inverted */
+ void assert(bool result, const char *format, ...);
/**
* @brief ReadEnv Reads all Maxscale and Master/Slave and Galera setups info from environmental variables
@@ -430,6 +434,15 @@ public:
*/
int try_query_all(int m, const char *sql);
+ /**
+ * @brief Get the set of labels that are assigned to server @c name
+ *
+ * @param name The name of the server that must be present in the output `maxadmin list servers`
+ *
+ * @return A set of string labels assigned to this server
+ */
+ StringSet get_server_status(const char* name);
+
/**
* @brief check_maxscale_processes Check if number of running Maxscale processes is equal to 'expected'
* @param expected expected number of Maxscale processes
@@ -478,6 +491,10 @@ public:
void check_current_connections(int m, int value);
int stop_maxscale(int m);
+ void process_template(const char *src, const char *dest = "/etc/maxscale.cnf");
+
+private:
+ void report_result(const char *format, va_list argp);
};
/**
diff --git a/maxscale-system-test/utilities.cmake b/maxscale-system-test/utilities.cmake
index c284958c7..e7be837da 100644
--- a/maxscale-system-test/utilities.cmake
+++ b/maxscale-system-test/utilities.cmake
@@ -91,7 +91,7 @@ add_test_executable_notest(sysbench_example.cpp sysbench_example replication)
# Build the MariaDB Connector/C 3.0
-set(CONNECTOR_C_VERSION "3.0" CACHE STRING "The Connector-C version to use")
+set(CONNECTOR_C_VERSION "v3.0.2" CACHE STRING "The Connector-C version to use")
include(ExternalProject)
ExternalProject_Add(connector-c
diff --git a/server/core/mysql_utils.cc b/server/core/mysql_utils.cc
index 6e0a26bcf..e49ef39b5 100644
--- a/server/core/mysql_utils.cc
+++ b/server/core/mysql_utils.cc
@@ -172,6 +172,19 @@ MYSQL *mxs_mysql_real_connect(MYSQL *con, SERVER *server, const char *user, cons
MY_CHARSET_INFO cs_info;
mysql_get_character_set_info(mysql, &cs_info);
server->charset = cs_info.number;
+
+ if (listener && mysql_get_ssl_cipher(con) == NULL)
+ {
+ if (server->log_warning.ssl_not_enabled)
+ {
+ server->log_warning.ssl_not_enabled = false;
+ MXS_ERROR("An encrypted connection to '%s' could not be created, "
+ "ensure that TLS is enabled on the target server.",
+ server->unique_name);
+ }
+ // Don't close the connection as it is closed elsewhere, just set to NULL
+ mysql = NULL;
+ }
}
return mysql;
diff --git a/server/core/server.cc b/server/core/server.cc
index 64823821a..95b17b612 100644
--- a/server/core/server.cc
+++ b/server/core/server.cc
@@ -149,6 +149,9 @@ SERVER* server_alloc(const char *name, const char *address, unsigned short port,
server->last_event = SERVER_UP_EVENT;
server->triggered_at = 0;
+ // Log all warnings once
+ memset(&server->log_warning, 1, sizeof(server->log_warning));
+
spinlock_acquire(&server_spin);
server->next = allServers;
allServers = server;
diff --git a/server/core/service.cc b/server/core/service.cc
index 399eb58dc..3385b3c2c 100644
--- a/server/core/service.cc
+++ b/server/core/service.cc
@@ -1607,6 +1607,7 @@ service_update(SERVICE *service, char *router, char *user, char *auth)
*/
int service_refresh_users(SERVICE *service)
{
+ ss_dassert(service);
int ret = 1;
int self = mxs_worker_get_current_id();
ss_dassert(self >= 0);
diff --git a/server/core/utils.cc b/server/core/utils.cc
index 3d595874f..043d84f43 100644
--- a/server/core/utils.cc
+++ b/server/core/utils.cc
@@ -588,7 +588,9 @@ strip_escape_chars(char* val)
#define BUFFER_GROWTH_RATE 2.0
static pcre2_code* remove_comments_re = NULL;
static const PCRE2_SPTR remove_comments_pattern = (PCRE2_SPTR)
- "(?:`[^`]*`\\K)|(\\/[*](?!(M?!)).*?[*]\\/)|(?:#.*|--[[:space:]].*)";
+ "(?:`[^`]*`\\K)|"
+ "(\\/[*](?!(M?!)).*?[*]\\/)|"
+ "([[:space:]](?:#.*|--[[:space:]].*(\\n|\\r\\n)))";
/**
* Remove SQL comments from the end of a string
diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h
index c0ca5f5e1..7673fe3e7 100644
--- a/server/modules/monitor/mysqlmon.h
+++ b/server/modules/monitor/mysqlmon.h
@@ -49,6 +49,7 @@ typedef struct
bool detectStaleMaster; /**< Monitor flag for MySQL replication Stale Master detection */
bool detectStaleSlave; /**< Monitor flag for MySQL replication Stale Master detection */
bool multimaster; /**< Detect and handle multi-master topologies */
+ bool ignore_external_masters; /**< Ignore masters outside of the monitor configuration */
int disableMasterFailback; /**< Monitor flag for Galera Cluster Master failback */
int availableWhenDonor; /**< Monitor flag for Galera Cluster Donor availability */
int disableMasterRoleSetting; /**< Monitor flag to disable setting master role */
diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c
new file mode 100644
index 000000000..14226c14b
--- /dev/null
+++ b/server/modules/monitor/mysqlmon/mysql_mon.c
@@ -0,0 +1,2109 @@
+/*
+ * Copyright (c) 2016 MariaDB Corporation Ab
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
+ *
+ * Change Date: 2019-07-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2 or later of the General
+ * Public License.
+ */
+
+/**
+ * @file mysql_mon.c - A MySQL replication cluster monitor
+ *
+ * @verbatim
+ * Revision History
+ *
+ * Date Who Description
+ * 08/07/13 Mark Riddoch Initial implementation
+ * 11/07/13 Mark Riddoch Addition of code to check replication status
+ * 25/07/13 Mark Riddoch Addition of decrypt for passwords and diagnostic interface
+ * 20/05/14 Massimiliano Pinto Addition of support for MariadDB multimaster replication setup.
+ * New server field version_string is updated.
+ * 28/05/14 Massimiliano Pinto Added set Id and configuration options (setInverval)
+ * Parameters are now printed in diagnostics
+ * 03/06/14 Mark Ridoch Add support for maintenance mode
+ * 17/06/14 Massimiliano Pinto Addition of getServerByNodeId routine and first implementation for
+ * depth of replication for nodes.
+ * 23/06/14 Massimiliano Pinto Added replication consistency after replication tree computation
+ * 27/06/14 Massimiliano Pinto Added replication pending status in monitored server, storing there
+ * the status to update in server status field before
+ * starting the replication consistency check.
+ * This will also give routers a consistent "status" of all servers
+ * 28/08/14 Massimiliano Pinto Added detectStaleMaster feature: previous detected master will be used again, even if the replication is stopped.
+ * This means both IO and SQL threads are not working on slaves.
+ * This option is not enabled by default.
+ * 10/11/14 Massimiliano Pinto Addition of setNetworkTimeout for connect, read, write
+ * 18/11/14 Massimiliano Pinto One server only in configuration becomes master. servers=server1 must
+ * be present in mysql_mon and in router sections as well.
+ * 08/05/15 Markus Makela Added launchable scripts
+ * 17/10/15 Martin Brampton Change DCB callback to hangup
+ *
+ * @endverbatim
+ */
+
+#define MXS_MODULE_NAME "mysqlmon"
+
+#include "../mysqlmon.h"
+#include
+#include
+#include
+#include
+#include
+
+/** Column positions for SHOW SLAVE STATUS */
+#define MYSQL55_STATUS_BINLOG_POS 5
+#define MYSQL55_STATUS_BINLOG_NAME 6
+#define MYSQL55_STATUS_IO_RUNNING 10
+#define MYSQL55_STATUS_SQL_RUNNING 11
+#define MYSQL55_STATUS_MASTER_ID 39
+
+/** Column positions for SHOW SLAVE STATUS */
+#define MARIA10_STATUS_BINLOG_NAME 7
+#define MARIA10_STATUS_BINLOG_POS 8
+#define MARIA10_STATUS_IO_RUNNING 12
+#define MARIA10_STATUS_SQL_RUNNING 13
+#define MARIA10_STATUS_MASTER_ID 41
+
+/** Column positions for SHOW SLAVE HOSTS */
+#define SLAVE_HOSTS_SERVER_ID 0
+#define SLAVE_HOSTS_HOSTNAME 1
+#define SLAVE_HOSTS_PORT 2
+
+static void monitorMain(void *);
+
+static void *startMonitor(MXS_MONITOR *, const MXS_CONFIG_PARAMETER*);
+static void stopMonitor(MXS_MONITOR *);
+static void diagnostics(DCB *, const MXS_MONITOR *);
+static MXS_MONITOR_SERVERS *getServerByNodeId(MXS_MONITOR_SERVERS *, long);
+static MXS_MONITOR_SERVERS *getSlaveOfNodeId(MXS_MONITOR_SERVERS *, long);
+static MXS_MONITOR_SERVERS *get_replication_tree(MXS_MONITOR *, int);
+static void set_master_heartbeat(MYSQL_MONITOR *, MXS_MONITOR_SERVERS *);
+static void set_slave_heartbeat(MXS_MONITOR *, MXS_MONITOR_SERVERS *);
+static int add_slave_to_master(long *, int, long);
+static bool isMySQLEvent(mxs_monitor_event_t event);
+void check_maxscale_schema_replication(MXS_MONITOR *monitor);
+static bool report_version_err = true;
+static const char* hb_table_name = "maxscale_schema.replication_heartbeat";
+
+/**
+ * The module entry point routine. It is this routine that
+ * must populate the structure that is referred to as the
+ * "module object", this is a structure with the set of
+ * external entry points for this module.
+ *
+ * @return The module object
+ */
+MXS_MODULE* MXS_CREATE_MODULE()
+{
+ MXS_NOTICE("Initialise the MySQL Monitor module.");
+
+ static MXS_MONITOR_OBJECT MyObject =
+ {
+ startMonitor,
+ stopMonitor,
+ diagnostics
+ };
+
+ static MXS_MODULE info =
+ {
+ MXS_MODULE_API_MONITOR,
+ MXS_MODULE_GA,
+ MXS_MONITOR_VERSION,
+ "A MySQL Master/Slave replication monitor",
+ "V1.5.0",
+ &MyObject,
+ NULL, /* Process init. */
+ NULL, /* Process finish. */
+ NULL, /* Thread init. */
+ NULL, /* Thread finish. */
+ {
+ {"detect_replication_lag", MXS_MODULE_PARAM_BOOL, "false"},
+ {"detect_stale_master", MXS_MODULE_PARAM_BOOL, "true"},
+ {"detect_stale_slave", MXS_MODULE_PARAM_BOOL, "true"},
+ {"mysql51_replication", MXS_MODULE_PARAM_BOOL, "false"},
+ {"multimaster", MXS_MODULE_PARAM_BOOL, "false"},
+ {"detect_standalone_master", MXS_MODULE_PARAM_BOOL, "false"},
+ {"failcount", MXS_MODULE_PARAM_COUNT, "5"},
+ {"allow_cluster_recovery", MXS_MODULE_PARAM_BOOL, "true"},
+ {"ignore_external_masters", MXS_MODULE_PARAM_BOOL, "false"},
+ {
+ "script",
+ MXS_MODULE_PARAM_PATH,
+ NULL,
+ MXS_MODULE_OPT_PATH_X_OK
+ },
+ {
+ "events",
+ MXS_MODULE_PARAM_ENUM,
+ MXS_MONITOR_EVENT_DEFAULT_VALUE,
+ MXS_MODULE_OPT_NONE,
+ mxs_monitor_event_enum_values
+ },
+ {MXS_END_MODULE_PARAMS}
+ }
+ };
+
+ return &info;
+}
+
+/**
+ * Monitor specific information about a server
+ */
+typedef struct mysql_server_info
+{
+ int server_id; /**< Value of @@server_id */
+ int master_id; /**< Master server id from SHOW SLAVE STATUS*/
+ int group; /**< Multi-master group where this server
+ belongs, 0 for servers not in groups */
+ bool read_only; /**< Value of @@read_only */
+ bool slave_configured; /**< Whether SHOW SLAVE STATUS returned rows */
+ bool slave_io; /**< If Slave IO thread is running */
+ bool slave_sql; /**< If Slave SQL thread is running */
+ uint64_t binlog_pos; /**< Binlog position from SHOW SLAVE STATUS */
+ char *binlog_name; /**< Binlog name from SHOW SLAVE STATUS */
+} MYSQL_SERVER_INFO;
+
+/** Other values are implicitly zero initialized */
+#define MYSQL_SERVER_INFO_INIT {.binlog_name = ""}
+
+void* info_copy_func(const void *val)
+{
+ ss_dassert(val);
+ MYSQL_SERVER_INFO *old_val = (MYSQL_SERVER_INFO*)val;
+ MYSQL_SERVER_INFO *new_val = MXS_MALLOC(sizeof(MYSQL_SERVER_INFO));
+ char *binlog_name = MXS_STRDUP(old_val->binlog_name);
+
+ if (new_val && binlog_name)
+ {
+ *new_val = *old_val;
+ new_val->binlog_name = binlog_name;
+ }
+ else
+ {
+ MXS_FREE(new_val);
+ MXS_FREE(binlog_name);
+ new_val = NULL;
+ }
+
+ return new_val;
+}
+
+void info_free_func(void *val)
+{
+ if (val)
+ {
+ MYSQL_SERVER_INFO *old_val = (MYSQL_SERVER_INFO*)val;
+ MXS_FREE(old_val->binlog_name);
+ MXS_FREE(old_val);
+ }
+}
+
+/**
+ * @brief Helper function that initializes the server info hashtable
+ *
+ * @param handle MySQL monitor handle
+ * @param database List of monitored databases
+ * @return True on success, false if initialization failed. At the moment
+ * initialization can only fail if memory allocation fails.
+ */
+bool init_server_info(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *database)
+{
+ MYSQL_SERVER_INFO info = MYSQL_SERVER_INFO_INIT;
+ bool rval = true;
+
+ while (database)
+ {
+ /** Delete any existing structures and replace them with empty ones */
+ hashtable_delete(handle->server_info, database->server->unique_name);
+
+ if (!hashtable_add(handle->server_info, database->server->unique_name, &info))
+ {
+ rval = false;
+ break;
+ }
+
+ database = database->next;
+ }
+
+ return rval;
+}
+
+/*lint +e14 */
+
+/**
+ * Start the instance of the monitor, returning a handle on the monitor.
+ *
+ * This function creates a thread to execute the actual monitoring.
+ *
+ * @param arg The current handle - NULL if first start
+ * @param opt Configuration parameters
+ * @return A handle to use when interacting with the monitor
+ */
+static void *
+startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params)
+{
+ MYSQL_MONITOR *handle = (MYSQL_MONITOR*) monitor->handle;
+
+ if (handle)
+ {
+ handle->shutdown = 0;
+ MXS_FREE(handle->script);
+ }
+ else
+ {
+ handle = (MYSQL_MONITOR *) MXS_MALLOC(sizeof(MYSQL_MONITOR));
+ HASHTABLE *server_info = hashtable_alloc(MAX_NUM_SLAVES, hashtable_item_strhash, hashtable_item_strcmp);
+
+ if (handle == NULL || server_info == NULL)
+ {
+ MXS_FREE(handle);
+ hashtable_free(server_info);
+ return NULL;
+ }
+
+ hashtable_memory_fns(server_info, hashtable_item_strdup, info_copy_func,
+ hashtable_item_free, info_free_func);
+ handle->server_info = server_info;
+ handle->shutdown = 0;
+ handle->id = config_get_global_options()->id;
+ handle->warn_failover = true;
+ spinlock_init(&handle->lock);
+ }
+
+ /** This should always be reset to NULL */
+ handle->master = NULL;
+
+ handle->detectStaleMaster = config_get_bool(params, "detect_stale_master");
+ handle->detectStaleSlave = config_get_bool(params, "detect_stale_slave");
+ handle->replicationHeartbeat = config_get_bool(params, "detect_replication_lag");
+ handle->multimaster = config_get_bool(params, "multimaster");
+ handle->ignore_external_masters = config_get_bool(params, "ignore_external_masters");
+ handle->detect_standalone_master = config_get_bool(params, "detect_standalone_master");
+ handle->failcount = config_get_integer(params, "failcount");
+ handle->allow_cluster_recovery = config_get_bool(params, "allow_cluster_recovery");
+ handle->mysql51_replication = config_get_bool(params, "mysql51_replication");
+ handle->script = config_copy_string(params, "script");
+ handle->events = config_get_enum(params, "events", mxs_monitor_event_enum_values);
+
+ bool error = false;
+
+ if (!check_monitor_permissions(monitor, "SHOW SLAVE STATUS"))
+ {
+ MXS_ERROR("Failed to start monitor. See earlier errors for more information.");
+ error = true;
+ }
+
+ if (!init_server_info(handle, monitor->databases))
+ {
+ error = true;
+ }
+
+ if (error)
+ {
+ hashtable_free(handle->server_info);
+ MXS_FREE(handle->script);
+ MXS_FREE(handle);
+ handle = NULL;
+ }
+ else if (thread_start(&handle->thread, monitorMain, monitor) == NULL)
+ {
+ MXS_ERROR("Failed to start monitor thread for monitor '%s'.", monitor->name);
+ }
+
+ return handle;
+}
+
+/**
+ * Stop a running monitor
+ *
+ * @param arg Handle on thr running monior
+ */
+static void
+stopMonitor(MXS_MONITOR *mon)
+{
+ MYSQL_MONITOR *handle = (MYSQL_MONITOR *) mon->handle;
+
+ handle->shutdown = 1;
+ thread_wait(handle->thread);
+}
+
+/**
+ * Daignostic interface
+ *
+ * @param dcb DCB to print diagnostics
+ * @param arg The monitor handle
+ */
+static void diagnostics(DCB *dcb, const MXS_MONITOR *mon)
+{
+ const MYSQL_MONITOR *handle = (const MYSQL_MONITOR *)mon->handle;
+
+ dcb_printf(dcb, "MaxScale MonitorId:\t%lu\n", handle->id);
+ dcb_printf(dcb, "Replication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled");
+ dcb_printf(dcb, "Detect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
+ dcb_printf(dcb, "Server information\n\n");
+
+ for (MXS_MONITOR_SERVERS *db = mon->databases; db; db = db->next)
+ {
+ MYSQL_SERVER_INFO *serv_info = hashtable_fetch(handle->server_info, db->server->unique_name);
+ dcb_printf(dcb, "Server: %s\n", db->server->unique_name);
+ dcb_printf(dcb, "Server ID: %d\n", serv_info->server_id);
+ dcb_printf(dcb, "Read only: %s\n", serv_info->read_only ? "ON" : "OFF");
+ dcb_printf(dcb, "Slave configured: %s\n", serv_info->slave_configured ? "YES" : "NO");
+ dcb_printf(dcb, "Slave IO running: %s\n", serv_info->slave_io ? "YES" : "NO");
+ dcb_printf(dcb, "Slave SQL running: %s\n", serv_info->slave_sql ? "YES" : "NO");
+ dcb_printf(dcb, "Master ID: %d\n", serv_info->master_id);
+ dcb_printf(dcb, "Master binlog file: %s\n", serv_info->binlog_name);
+ dcb_printf(dcb, "Master binlog position: %lu\n", serv_info->binlog_pos);
+
+ if (handle->multimaster)
+ {
+ dcb_printf(dcb, "Master group: %d\n", serv_info->group);
+ }
+
+ dcb_printf(dcb, "\n");
+ }
+}
+
+enum mysql_server_version
+{
+ MYSQL_SERVER_VERSION_100,
+ MYSQL_SERVER_VERSION_55,
+ MYSQL_SERVER_VERSION_51
+};
+
+static inline void monitor_mysql_db(MXS_MONITOR_SERVERS* database, MYSQL_SERVER_INFO *serv_info,
+ enum mysql_server_version server_version)
+{
+ int columns, i_io_thread, i_sql_thread, i_binlog_pos, i_master_id, i_binlog_name;
+ const char *query;
+
+ if (server_version == MYSQL_SERVER_VERSION_100)
+ {
+ columns = 42;
+ query = "SHOW ALL SLAVES STATUS";
+ i_io_thread = MARIA10_STATUS_IO_RUNNING;
+ i_sql_thread = MARIA10_STATUS_SQL_RUNNING;
+ i_binlog_name = MARIA10_STATUS_BINLOG_NAME;
+ i_binlog_pos = MARIA10_STATUS_BINLOG_POS;
+ i_master_id = MARIA10_STATUS_MASTER_ID;
+ }
+ else
+ {
+ columns = server_version == MYSQL_SERVER_VERSION_55 ? 40 : 38;
+ query = "SHOW SLAVE STATUS";
+ i_io_thread = MYSQL55_STATUS_IO_RUNNING;
+ i_sql_thread = MYSQL55_STATUS_SQL_RUNNING;
+ i_binlog_name = MYSQL55_STATUS_BINLOG_NAME;
+ i_binlog_pos = MYSQL55_STATUS_BINLOG_POS;
+ i_master_id = MYSQL55_STATUS_MASTER_ID;
+ }
+
+ /** Clear old states */
+ monitor_clear_pending_status(database, SERVER_SLAVE | SERVER_MASTER | SERVER_RELAY_MASTER |
+ SERVER_STALE_STATUS | SERVER_SLAVE_OF_EXTERNAL_MASTER);
+
+ MYSQL_RES* result;
+
+ if (mxs_mysql_query(database->con, query) == 0
+ && (result = mysql_store_result(database->con)) != NULL)
+ {
+ if (mysql_field_count(database->con) < columns)
+ {
+ mysql_free_result(result);
+ MXS_ERROR("\"%s\" returned less than the expected amount of columns. "
+ "Expected %d columns.", query, columns);
+ return;
+ }
+
+ MYSQL_ROW row = mysql_fetch_row(result);
+ long master_id = -1;
+
+ if (row)
+ {
+ serv_info->slave_configured = true;
+ int nconfigured = 0;
+ int nrunning = 0;
+
+ do
+ {
+ /* get Slave_IO_Running and Slave_SQL_Running values*/
+ serv_info->slave_io = strncmp(row[i_io_thread], "Yes", 3) == 0;
+ serv_info->slave_sql = strncmp(row[i_sql_thread], "Yes", 3) == 0;
+
+ if (serv_info->slave_io && serv_info->slave_sql)
+ {
+ if (nrunning == 0)
+ {
+ /** Only check binlog name for the first running slave */
+ char *binlog_name = MXS_STRDUP(row[i_binlog_name]);
+
+ if (binlog_name)
+ {
+ MXS_FREE(serv_info->binlog_name);
+ serv_info->binlog_name = binlog_name;
+ serv_info->binlog_pos = atol(row[i_binlog_pos]);
+ }
+ }
+
+ nrunning++;
+ }
+
+ /* If Slave_IO_Running = Yes, assign the master_id to current server: this allows building
+ * the replication tree, slaves ids will be added to master(s) and we will have at least the
+ * root master server.
+ * Please note, there could be no slaves at all if Slave_SQL_Running == 'No'
+ */
+ if (serv_info->slave_io && server_version != MYSQL_SERVER_VERSION_51)
+ {
+ /* Get Master_Server_Id */
+ master_id = atol(row[i_master_id]);
+ if (master_id == 0)
+ {
+ master_id = -1;
+ }
+ }
+
+ nconfigured++;
+ row = mysql_fetch_row(result);
+ }
+ while (row);
+
+
+ /* If all configured slaves are running set this node as slave */
+ if (nrunning > 0 && nrunning == nconfigured)
+ {
+ monitor_set_pending_status(database, SERVER_SLAVE);
+ }
+ }
+ else
+ {
+ /** Query returned no rows, replication is not configured */
+ serv_info->slave_configured = false;
+ serv_info->slave_io = false;
+ serv_info->slave_sql = false;
+ serv_info->binlog_pos = 0;
+ serv_info->binlog_name[0] = '\0';
+ }
+
+ /** Store master_id of current node. For MySQL 5.1 it will be set at a later point. */
+ database->server->master_id = master_id;
+ serv_info->master_id = master_id;
+
+ mysql_free_result(result);
+ }
+ else
+ {
+ mon_report_query_error(database);
+ }
+}
+
+/**
+ * Build the replication tree for a MySQL 5.1 cluster
+ *
+ * This function queries each server with SHOW SLAVE HOSTS to determine which servers
+ * have slaves replicating from them.
+ * @param mon Monitor
+ * @return Lowest server ID master in the monitor
+ */
+static MXS_MONITOR_SERVERS *build_mysql51_replication_tree(MXS_MONITOR *mon)
+{
+ MXS_MONITOR_SERVERS* database = mon->databases;
+ MXS_MONITOR_SERVERS *ptr, *rval = NULL;
+ int i;
+ MYSQL_MONITOR *handle = mon->handle;
+
+ while (database)
+ {
+ bool ismaster = false;
+ MYSQL_RES* result;
+ MYSQL_ROW row;
+ int nslaves = 0;
+ if (database->con)
+ {
+ if (mxs_mysql_query(database->con, "SHOW SLAVE HOSTS") == 0
+ && (result = mysql_store_result(database->con)) != NULL)
+ {
+ if (mysql_field_count(database->con) < 4)
+ {
+ mysql_free_result(result);
+ MXS_ERROR("\"SHOW SLAVE HOSTS\" "
+ "returned less than the expected amount of columns. "
+ "Expected 4 columns.");
+ return NULL;
+ }
+
+ if (mysql_num_rows(result) > 0)
+ {
+ ismaster = true;
+ while (nslaves < MAX_NUM_SLAVES && (row = mysql_fetch_row(result)))
+ {
+ /* get Slave_IO_Running and Slave_SQL_Running values*/
+ database->server->slaves[nslaves] = atol(row[SLAVE_HOSTS_SERVER_ID]);
+ nslaves++;
+ MXS_DEBUG("Found slave at %s:%s", row[SLAVE_HOSTS_HOSTNAME], row[SLAVE_HOSTS_PORT]);
+ }
+ database->server->slaves[nslaves] = 0;
+ }
+
+ mysql_free_result(result);
+ }
+ else
+ {
+ mon_report_query_error(database);
+ }
+
+ /* Set the Slave Role */
+ if (ismaster)
+ {
+ handle->master = database;
+
+ MXS_DEBUG("Master server found at [%s]:%d with %d slaves",
+ database->server->name,
+ database->server->port,
+ nslaves);
+
+ monitor_set_pending_status(database, SERVER_MASTER);
+ database->server->depth = 0; // Add Depth 0 for Master
+
+ if (rval == NULL || rval->server->node_id > database->server->node_id)
+ {
+ rval = database;
+ }
+ }
+ }
+ database = database->next;
+ }
+
+ database = mon->databases;
+
+ /** Set master server IDs */
+ while (database)
+ {
+ ptr = mon->databases;
+
+ while (ptr)
+ {
+ for (i = 0; ptr->server->slaves[i]; i++)
+ {
+ if (ptr->server->slaves[i] == database->server->node_id)
+ {
+ database->server->master_id = ptr->server->node_id;
+ database->server->depth = 1; // Add Depth 1 for Slave
+ break;
+ }
+ }
+ ptr = ptr->next;
+ }
+ if (SERVER_IS_SLAVE(database->server) &&
+ (database->server->master_id <= 0 ||
+ database->server->master_id != handle->master->server->node_id))
+ {
+ monitor_set_pending_status(database, SERVER_SLAVE);
+ monitor_set_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER);
+ }
+ database = database->next;
+ }
+ return rval;
+}
+
+/**
+ * Monitor an individual server
+ *
+ * @param handle The MySQL Monitor object
+ * @param database The database to probe
+ */
+static void
+monitorDatabase(MXS_MONITOR *mon, MXS_MONITOR_SERVERS *database)
+{
+ MYSQL_MONITOR* handle = mon->handle;
+ MYSQL_ROW row;
+ MYSQL_RES *result;
+ unsigned long int server_version = 0;
+ char *server_string;
+
+ /* Don't probe servers in maintenance mode */
+ if (SERVER_IN_MAINT(database->server))
+ {
+ return;
+ }
+
+ /** Store previous status */
+ database->mon_prev_status = database->server->status;
+
+ if (database->con == NULL || mysql_ping(database->con) != 0)
+ {
+ mxs_connect_result_t rval;
+ if ((rval = mon_connect_to_db(mon, database)) == MONITOR_CONN_OK)
+ {
+ server_clear_status_nolock(database->server, SERVER_AUTH_ERROR);
+ monitor_clear_pending_status(database, SERVER_AUTH_ERROR);
+ }
+ else
+ {
+ /* The current server is not running
+ *
+ * Store server NOT running in server and monitor server pending struct
+ *
+ */
+ if (mysql_errno(database->con) == ER_ACCESS_DENIED_ERROR)
+ {
+ server_set_status_nolock(database->server, SERVER_AUTH_ERROR);
+ monitor_set_pending_status(database, SERVER_AUTH_ERROR);
+ }
+ server_clear_status_nolock(database->server, SERVER_RUNNING);
+ monitor_clear_pending_status(database, SERVER_RUNNING);
+
+ /* Also clear M/S state in both server and monitor server pending struct */
+ server_clear_status_nolock(database->server, SERVER_SLAVE);
+ server_clear_status_nolock(database->server, SERVER_MASTER);
+ server_clear_status_nolock(database->server, SERVER_RELAY_MASTER);
+ monitor_clear_pending_status(database, SERVER_SLAVE);
+ monitor_clear_pending_status(database, SERVER_MASTER);
+ monitor_clear_pending_status(database, SERVER_RELAY_MASTER);
+
+ /* Clean addition status too */
+ server_clear_status_nolock(database->server, SERVER_SLAVE_OF_EXTERNAL_MASTER);
+ server_clear_status_nolock(database->server, SERVER_STALE_STATUS);
+ server_clear_status_nolock(database->server, SERVER_STALE_SLAVE);
+ monitor_clear_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER);
+ monitor_clear_pending_status(database, SERVER_STALE_STATUS);
+ monitor_clear_pending_status(database, SERVER_STALE_SLAVE);
+
+ /* Log connect failure only once */
+ if (mon_status_changed(database) && mon_print_fail_status(database))
+ {
+ mon_log_connect_error(database, rval);
+ }
+
+ return;
+ }
+ }
+ /* Store current status in both server and monitor server pending struct */
+ server_set_status_nolock(database->server, SERVER_RUNNING);
+ monitor_set_pending_status(database, SERVER_RUNNING);
+
+ /* get server version from current server */
+ server_version = mysql_get_server_version(database->con);
+
+ /* get server version string */
+ server_string = (char *) mysql_get_server_info(database->con);
+ if (server_string)
+ {
+ server_set_version_string(database->server, server_string);
+ }
+
+ MYSQL_SERVER_INFO *serv_info = hashtable_fetch(handle->server_info, database->server->unique_name);
+ ss_dassert(serv_info);
+
+ /* Get server_id and read_only from current node */
+ if (mxs_mysql_query(database->con, "SELECT @@server_id, @@read_only") == 0
+ && (result = mysql_store_result(database->con)) != NULL)
+ {
+ long server_id = -1;
+
+ if (mysql_field_count(database->con) != 2)
+ {
+ mysql_free_result(result);
+ MXS_ERROR("Unexpected result for 'SELECT @@server_id, @@read_only'. Expected 2 columns."
+ " MySQL Version: %s", server_string);
+ return;
+ }
+
+ while ((row = mysql_fetch_row(result)))
+ {
+ server_id = strtol(row[0], NULL, 10);
+ if ((errno == ERANGE && (server_id == LONG_MAX
+ || server_id == LONG_MIN)) || (errno != 0 && server_id == 0))
+ {
+ server_id = -1;
+ }
+
+ database->server->node_id = server_id;
+ serv_info->server_id = server_id;
+ serv_info->read_only = (row[1] && strcmp(row[1], "1") == 0);
+ }
+ mysql_free_result(result);
+ }
+ else
+ {
+ mon_report_query_error(database);
+ }
+
+ /* Check first for MariaDB 10.x.x and get status for multi-master replication */
+ if (server_version >= 100000)
+ {
+ monitor_mysql_db(database, serv_info, MYSQL_SERVER_VERSION_100);
+ }
+ else if (server_version >= 5 * 10000 + 5 * 100)
+ {
+ monitor_mysql_db(database, serv_info, MYSQL_SERVER_VERSION_55);
+ }
+ else
+ {
+ if (handle->mysql51_replication)
+ {
+ monitor_mysql_db(database, serv_info, MYSQL_SERVER_VERSION_51);
+ }
+ else if (report_version_err)
+ {
+ report_version_err = false;
+ MXS_ERROR("MySQL version is lower than 5.5 and 'mysql51_replication' option is "
+ "not enabled, replication tree cannot be resolved. To enable MySQL 5.1 replication "
+ "detection, add 'mysql51_replication=true' to the monitor section.");
+ }
+ }
+
+}
+
+/**
+ * @brief A node in a graph
+ */
+struct graph_node
+{
+ int index;
+ int lowest_index;
+ int cycle;
+ bool active;
+ struct graph_node *parent;
+ MYSQL_SERVER_INFO *info;
+ MXS_MONITOR_SERVERS *db;
+};
+
+/**
+ * @brief Visit a node in the graph
+ *
+ * This function is the main function used to determine whether the node is a
+ * part of a cycle. It is an implementation of the Tarjan's strongly connected
+ * component algorithm. All one node cycles are ignored since normal
+ * master-slave monitoring handles that.
+ *
+ * Tarjan's strongly connected component algorithm:
+ *
+ * https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+ */
+static void visit_node(struct graph_node *node, struct graph_node **stack,
+ int *stacksize, int *index, int *cycle)
+{
+ /** Assign an index to this node */
+ node->lowest_index = node->index = *index;
+ node->active = true;
+ *index += 1;
+
+ stack[*stacksize] = node;
+ *stacksize += 1;
+
+ if (node->parent == NULL)
+ {
+ /** This node does not connect to another node, it can't be a part of a cycle */
+ node->lowest_index = -1;
+ }
+ else if (node->parent->index == 0)
+ {
+ /** Node has not been visited */
+ visit_node(node->parent, stack, stacksize, index, cycle);
+
+ if (node->parent->lowest_index < node->lowest_index)
+ {
+ /** The parent connects to a node with a lower index, this node
+ could be a part of a cycle. */
+ node->lowest_index = node->parent->lowest_index;
+ }
+ }
+ else if (node->parent->active)
+ {
+ /** This node could be a root node of the cycle */
+ if (node->parent->index < node->lowest_index)
+ {
+ /** Root node found */
+ node->lowest_index = node->parent->index;
+ }
+ }
+ else
+ {
+ /** Node connects to an already connected cycle, it can't be a part of it */
+ node->lowest_index = -1;
+ }
+
+ if (node->active && node->parent && node->lowest_index > 0)
+ {
+ if (node->lowest_index == node->index &&
+ node->lowest_index == node->parent->lowest_index)
+ {
+ /**
+ * Found a multi-node cycle from the graph. The cycle is formed from the
+ * nodes with a lowest_index value equal to the lowest_index value of the
+ * current node. Rest of the nodes on the stack are not part of a cycle
+ * and can be discarded.
+ */
+
+ *cycle += 1;
+
+ while (*stacksize > 0)
+ {
+ struct graph_node *top = stack[(*stacksize) - 1];
+ top->active = false;
+
+ if (top->lowest_index == node->lowest_index)
+ {
+ top->cycle = *cycle;
+ }
+ *stacksize -= 1;
+ }
+ }
+ }
+ else
+ {
+ /** Pop invalid nodes off the stack */
+ node->active = false;
+ if (*stacksize > 0)
+ {
+ *stacksize -= 1;
+ }
+ }
+}
+
+/**
+ * @brief Find the strongly connected components in the replication tree graph
+ *
+ * Each replication cluster is a directed graph made out of replication
+ * trees. If this graph has strongly connected components (more generally
+ * cycles), it is considered a multi-master cluster due to the fact that there
+ * are multiple nodes where the data can originate.
+ *
+ * Detecting the cycles in the graph allows this monitor to better understand
+ * the relationships between the nodes. All nodes that are a part of a cycle can
+ * be labeled as master nodes. This information will later be used to choose the
+ * right master where the writes should go.
+ *
+ * This function also populates the MYSQL_SERVER_INFO structures group
+ * member. Nodes in a group get a positive group ID where the nodes not in a
+ * group get a group ID of 0.
+ */
+void find_graph_cycles(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *database, int nservers)
+{
+ struct graph_node graph[nservers];
+ struct graph_node *stack[nservers];
+ int nodes = 0;
+
+ for (MXS_MONITOR_SERVERS *db = database; db; db = db->next)
+ {
+ graph[nodes].info = hashtable_fetch(handle->server_info, db->server->unique_name);
+ graph[nodes].db = db;
+ ss_dassert(graph[nodes].info);
+ graph[nodes].index = graph[nodes].lowest_index = 0;
+ graph[nodes].cycle = 0;
+ graph[nodes].active = false;
+ graph[nodes].parent = NULL;
+ nodes++;
+ }
+
+ /** Build the graph */
+ for (int i = 0; i < nservers; i++)
+ {
+ if (graph[i].info->master_id > 0)
+ {
+ /** Found a connected node */
+ for (int k = 0; k < nservers; k++)
+ {
+ if (graph[k].info->server_id == graph[i].info->master_id)
+ {
+ graph[i].parent = &graph[k];
+ break;
+ }
+ }
+ }
+ }
+
+ int index = 1;
+ int cycle = 0;
+ int stacksize = 0;
+
+ for (int i = 0; i < nservers; i++)
+ {
+ if (graph[i].index == 0)
+ {
+ /** Index is 0, this node has not yet been visited */
+ visit_node(&graph[i], stack, &stacksize, &index, &cycle);
+ }
+ }
+
+ for (int i = 0; i < nservers; i++)
+ {
+ graph[i].info->group = graph[i].cycle;
+
+ if (graph[i].cycle > 0)
+ {
+ /** We have at least one cycle in the graph */
+ if (graph[i].info->read_only)
+ {
+ monitor_set_pending_status(graph[i].db, SERVER_SLAVE);
+ monitor_clear_pending_status(graph[i].db, SERVER_MASTER);
+ }
+ else
+ {
+ monitor_set_pending_status(graph[i].db, SERVER_MASTER);
+ monitor_clear_pending_status(graph[i].db, SERVER_SLAVE);
+ }
+ }
+ else if (handle->detectStaleMaster && cycle == 0 &&
+ graph[i].db->server->status & SERVER_MASTER &&
+ (graph[i].db->pending_status & SERVER_MASTER) == 0)
+ {
+ /**
+ * Stale master detection is handled here for multi-master mode.
+ *
+ * If we know that no cycles were found from the graph and that a
+ * server once had the master status, replication has broken
+ * down. These masters are assigned the stale master status allowing
+ * them to be used as masters even if they lose their slaves. A
+ * slave in this case can be either a normal slave or another
+ * master.
+ */
+ if (graph[i].info->read_only)
+ {
+ /** The master is in read-only mode, set it into Slave state */
+ monitor_set_pending_status(graph[i].db, SERVER_SLAVE);
+ monitor_clear_pending_status(graph[i].db, SERVER_MASTER | SERVER_STALE_STATUS);
+ }
+ else
+ {
+ monitor_set_pending_status(graph[i].db, SERVER_MASTER | SERVER_STALE_STATUS);
+ monitor_clear_pending_status(graph[i].db, SERVER_SLAVE);
+ }
+ }
+ }
+}
+
+/**
+ * @brief Check whether failover conditions have been met
+ *
+ * This function checks whether all the conditions to trigger a failover have
+ * been met. For a failover to happen, only one server must be available and
+ * other servers must have passed the configured tolerance level of failures.
+ *
+ * @param handle Monitor instance
+ * @param db Monitor servers
+ *
+ * @return True if failover is required
+ */
+bool failover_required(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *db)
+{
+ int candidates = 0;
+
+ while (db)
+ {
+ if (SERVER_IS_RUNNING(db->server))
+ {
+ candidates++;
+ MYSQL_SERVER_INFO *server_info = hashtable_fetch(handle->server_info, db->server->unique_name);
+
+ if (server_info->read_only || server_info->slave_configured || candidates > 1)
+ {
+ return false;
+ }
+ }
+ else if (db->mon_err_count < handle->failcount)
+ {
+ return false;
+ }
+
+ db = db->next;
+ }
+
+ return candidates == 1;
+}
+
+/**
+ * @brief Initiate simple failover
+ *
+ * This function does the actual failover by assigning the last remaining server
+ * the master status and setting all other servers into maintenance mode. By
+ * setting the servers into maintenance mode, we prevent any possible conflicts
+ * when the failed servers come back up.
+ *
+ * @param handle Monitor instance
+ * @param db Monitor servers
+ */
+void do_failover(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *db)
+{
+ while (db)
+ {
+ if (SERVER_IS_RUNNING(db->server))
+ {
+ if (!SERVER_IS_MASTER(db->server) && handle->warn_failover)
+ {
+ MXS_WARNING("Failover initiated, server '%s' is now the master.%s",
+ db->server->unique_name,
+ handle->allow_cluster_recovery ?
+ "" : " All other servers are set into maintenance mode.");
+ handle->warn_failover = false;
+ }
+
+ server_clear_set_status(db->server, SERVER_SLAVE, SERVER_MASTER | SERVER_STALE_STATUS);
+ monitor_set_pending_status(db, SERVER_MASTER | SERVER_STALE_STATUS);
+ monitor_clear_pending_status(db, SERVER_SLAVE);
+ handle->master = db;
+ }
+ else if (!handle->allow_cluster_recovery)
+ {
+ server_set_status_nolock(db->server, SERVER_MAINT);
+ monitor_set_pending_status(db, SERVER_MAINT);
+ }
+ db = db->next;
+ }
+}
+
+/**
+ * The entry point for the monitoring module thread
+ *
+ * @param arg The handle of the monitor
+ */
+static void
+monitorMain(void *arg)
+{
+ MXS_MONITOR* mon = (MXS_MONITOR*) arg;
+ MYSQL_MONITOR *handle;
+ MXS_MONITOR_SERVERS *ptr;
+ int replication_heartbeat;
+ bool detect_stale_master;
+ int num_servers = 0;
+ MXS_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;
+ spinlock_release(&mon->lock);
+ replication_heartbeat = handle->replicationHeartbeat;
+ detect_stale_master = handle->detectStaleMaster;
+
+ if (mysql_thread_init())
+ {
+ MXS_ERROR("mysql_thread_init failed in monitor module. Exiting.");
+ return;
+ }
+ handle->status = MXS_MONITOR_RUNNING;
+
+ while (1)
+ {
+ if (handle->shutdown)
+ {
+ handle->status = MXS_MONITOR_STOPPING;
+ mysql_thread_end();
+ handle->status = MXS_MONITOR_STOPPED;
+ return;
+ }
+ /** Wait base interval */
+ thread_millisleep(MXS_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
+ * interval, then skip monitoring checks. Excluding the first
+ * round.
+ */
+ if (nrounds != 0 &&
+ (((nrounds * MXS_MON_BASE_INTERVAL_MS) % mon->interval) >=
+ MXS_MON_BASE_INTERVAL_MS) && (!mon->server_pending_changes))
+ {
+ nrounds += 1;
+ continue;
+ }
+ nrounds += 1;
+ /* reset num_servers */
+ num_servers = 0;
+
+ lock_monitor_servers(mon);
+ servers_status_pending_to_current(mon);
+
+ /* start from the first server in the list */
+ ptr = mon->databases;
+
+ while (ptr)
+ {
+ ptr->mon_prev_status = ptr->server->status;
+
+ /* copy server status into monitor pending_status */
+ ptr->pending_status = ptr->server->status;
+
+ /* monitor current node */
+ monitorDatabase(mon, ptr);
+
+ /* reset the slave list of current node */
+ memset(&ptr->server->slaves, 0, sizeof(ptr->server->slaves));
+
+ num_servers++;
+
+ if (mon_status_changed(ptr))
+ {
+ if (SRV_MASTER_STATUS(ptr->mon_prev_status))
+ {
+ /** Master failed, can't recover */
+ MXS_NOTICE("Server [%s]:%d lost the master status.",
+ ptr->server->name,
+ ptr->server->port);
+ }
+ }
+
+ if (mon_status_changed(ptr))
+ {
+#if defined(SS_DEBUG)
+ MXS_INFO("Backend server [%s]:%d state : %s",
+ ptr->server->name,
+ ptr->server->port,
+ STRSRVSTATUS(ptr->server));
+#else
+ MXS_DEBUG("Backend server [%s]:%d state : %s",
+ ptr->server->name,
+ ptr->server->port,
+ STRSRVSTATUS(ptr->server));
+#endif
+ }
+
+ if (SERVER_IS_DOWN(ptr->server))
+ {
+ /** Increase this server'e error count */
+ ptr->mon_err_count += 1;
+ }
+ else
+ {
+ /** Reset this server's error count */
+ ptr->mon_err_count = 0;
+ }
+
+ ptr = ptr->next;
+ }
+
+ ptr = mon->databases;
+ /* if only one server is configured, that's is Master */
+ if (num_servers == 1)
+ {
+ if (SERVER_IS_RUNNING(ptr->server))
+ {
+ ptr->server->depth = 0;
+ /* status cleanup */
+ monitor_clear_pending_status(ptr, SERVER_SLAVE);
+
+ /* master status set */
+ monitor_set_pending_status(ptr, SERVER_MASTER);
+
+ ptr->server->depth = 0;
+ handle->master = ptr;
+ root_master = ptr;
+ }
+ }
+ else
+ {
+ /* Compute the replication tree */
+ if (handle->mysql51_replication)
+ {
+ root_master = build_mysql51_replication_tree(mon);
+ }
+ else
+ {
+ root_master = get_replication_tree(mon, num_servers);
+ }
+
+ }
+
+ if (handle->multimaster && num_servers > 0)
+ {
+ /** Find all the master server cycles in the cluster graph. If
+ multiple masters are found, the servers with the read_only
+ variable set to ON will be assigned the slave status. */
+ find_graph_cycles(handle, mon->databases, num_servers);
+ }
+
+ ptr = mon->databases;
+ while (ptr)
+ {
+ MYSQL_SERVER_INFO *serv_info = hashtable_fetch(handle->server_info, ptr->server->unique_name);
+ ss_dassert(serv_info);
+
+ if (ptr->server->node_id > 0 && ptr->server->master_id > 0 &&
+ getSlaveOfNodeId(mon->databases, ptr->server->node_id) &&
+ getServerByNodeId(mon->databases, ptr->server->master_id) &&
+ (!handle->multimaster || serv_info->group == 0))
+ {
+ /** This server is both a slave and a master i.e. a relay master */
+ monitor_set_pending_status(ptr, SERVER_RELAY_MASTER);
+ monitor_clear_pending_status(ptr, SERVER_MASTER);
+ }
+ ptr = ptr->next;
+ }
+
+ /* Update server status from monitor pending status on that server*/
+
+ ptr = mon->databases;
+ while (ptr)
+ {
+ if (!SERVER_IN_MAINT(ptr->server))
+ {
+ MYSQL_SERVER_INFO *serv_info = hashtable_fetch(handle->server_info, ptr->server->unique_name);
+ ss_dassert(serv_info);
+
+ /** If "detect_stale_master" option is On, let's use the previous master.
+ *
+ * Multi-master mode detects the stale masters in find_graph_cycles().
+ */
+ if (detect_stale_master && root_master && !handle->multimaster &&
+ (strcmp(ptr->server->name, root_master->server->name) == 0 &&
+ ptr->server->port == root_master->server->port) &&
+ (ptr->server->status & SERVER_MASTER) &&
+ !(ptr->pending_status & SERVER_MASTER) &&
+ !serv_info->read_only)
+ {
+ /**
+ * In this case server->status will not be updated from pending_status
+ * Set the STALE bit for this server in server struct
+ */
+ server_set_status_nolock(ptr->server, SERVER_STALE_STATUS | SERVER_MASTER);
+ ptr->pending_status |= SERVER_STALE_STATUS | SERVER_MASTER;
+
+ /** Log the message only if the master server didn't have
+ * the stale master bit set */
+ if ((ptr->mon_prev_status & SERVER_STALE_STATUS) == 0)
+ {
+ MXS_WARNING("All slave servers under the current master "
+ "server have been lost. Assigning Stale Master"
+ " status to the old master server '%s' (%s:%i).",
+ ptr->server->unique_name, ptr->server->name,
+ ptr->server->port);
+ }
+ }
+
+ if (handle->detectStaleSlave)
+ {
+ int bits = SERVER_SLAVE | SERVER_RUNNING;
+
+ if ((ptr->mon_prev_status & bits) == bits &&
+ root_master && SERVER_IS_MASTER(root_master->server))
+ {
+ /** Slave with a running master, assign stale slave candidacy */
+ if ((ptr->pending_status & bits) == bits)
+ {
+ ptr->pending_status |= SERVER_STALE_SLAVE;
+ }
+ /** Server lost slave when a master is available, remove
+ * stale slave candidacy */
+ else if ((ptr->pending_status & bits) == SERVER_RUNNING)
+ {
+ ptr->pending_status &= ~SERVER_STALE_SLAVE;
+ }
+ }
+ /** If this server was a stale slave candidate, assign
+ * slave status to it */
+ else if (ptr->mon_prev_status & SERVER_STALE_SLAVE &&
+ ptr->pending_status & SERVER_RUNNING &&
+ // Master is down
+ (!root_master || !SERVER_IS_MASTER(root_master->server) ||
+ // Master just came up
+ (SERVER_IS_MASTER(root_master->server) &&
+ (root_master->mon_prev_status & SERVER_MASTER) == 0)))
+ {
+ ptr->pending_status |= SERVER_SLAVE;
+ }
+ else if (root_master == NULL && serv_info->slave_configured)
+ {
+ ptr->pending_status |= SERVER_SLAVE;
+ }
+ }
+
+ ptr->server->status = ptr->pending_status;
+ }
+ ptr = ptr->next;
+ }
+
+ /** Now that all servers have their status correctly set, we can check
+ if we need to do a failover */
+ if (handle->detect_standalone_master)
+ {
+ if (failover_required(handle, mon->databases))
+ {
+ /** Other servers have died, initiate a failover to the last remaining server */
+ do_failover(handle, mon->databases);
+ }
+ else
+ {
+ handle->warn_failover = true;
+ }
+ }
+
+ /**
+ * Clear external slave status from master if configured to do so.
+ * This allows parts of a multi-tiered replication setup to be used
+ * in MaxScale.
+ */
+ if (root_master && SERVER_IS_SLAVE_OF_EXTERNAL_MASTER(root_master->server) &&
+ SERVER_IS_MASTER(root_master->server) && 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);
+ }
+
+ /**
+ * After updating the status of all servers, check if monitor events
+ * need to be launched.
+ */
+ mon_process_state_changes(mon, handle->script, handle->events);
+
+ /* log master detection failure of first master becomes available after failure */
+ if (root_master &&
+ mon_status_changed(root_master) &&
+ !(root_master->server->status & SERVER_STALE_STATUS))
+ {
+ if (root_master->pending_status & (SERVER_MASTER) && SERVER_IS_RUNNING(root_master->server))
+ {
+ if (!(root_master->mon_prev_status & SERVER_STALE_STATUS) &&
+ !(root_master->server->status & SERVER_MAINT))
+ {
+ MXS_NOTICE("A Master Server is now available: %s:%i",
+ root_master->server->name,
+ root_master->server->port);
+ }
+ }
+ else
+ {
+ MXS_ERROR("No Master can be determined. Last known was %s:%i",
+ root_master->server->name,
+ root_master->server->port);
+ }
+ log_no_master = 1;
+ }
+ else
+ {
+ if (!root_master && log_no_master)
+ {
+ MXS_ERROR("No Master can be determined");
+ log_no_master = 0;
+ }
+ }
+
+ /* Do now the heartbeat replication set/get for MySQL Replication Consistency */
+ if (replication_heartbeat &&
+ root_master &&
+ (SERVER_IS_MASTER(root_master->server) ||
+ SERVER_IS_RELAY_SERVER(root_master->server)))
+ {
+ set_master_heartbeat(handle, root_master);
+ ptr = mon->databases;
+
+ while (ptr)
+ {
+ if ((!SERVER_IN_MAINT(ptr->server)) && SERVER_IS_RUNNING(ptr->server))
+ {
+ if (ptr->server->node_id != root_master->server->node_id &&
+ (SERVER_IS_SLAVE(ptr->server) ||
+ SERVER_IS_RELAY_SERVER(ptr->server)))
+ {
+ set_slave_heartbeat(mon, ptr);
+ }
+ }
+ ptr = ptr->next;
+ }
+ }
+
+ mon_hangup_failed_servers(mon);
+ servers_status_current_to_pending(mon);
+ release_monitor_servers(mon);
+ } /*< while (1) */
+}
+
+/**
+ * Fetch a MySQL node by node_id
+ *
+ * @param ptr The list of servers to monitor
+ * @param node_id The MySQL server_id to fetch
+ * @return The server with the required server_id
+ */
+static MXS_MONITOR_SERVERS *
+getServerByNodeId(MXS_MONITOR_SERVERS *ptr, long node_id)
+{
+ SERVER *current;
+ while (ptr)
+ {
+ current = ptr->server;
+ if (current->node_id == node_id)
+ {
+ return ptr;
+ }
+ ptr = ptr->next;
+ }
+ return NULL;
+}
+
+/**
+ * Fetch a MySQL slave node from a node_id
+ *
+ * @param ptr The list of servers to monitor
+ * @param node_id The MySQL server_id to fetch
+ * @return The slave server of this node_id
+ */
+static MXS_MONITOR_SERVERS *
+getSlaveOfNodeId(MXS_MONITOR_SERVERS *ptr, long node_id)
+{
+ SERVER *current;
+ while (ptr)
+ {
+ current = ptr->server;
+ if (current->master_id == node_id)
+ {
+ return ptr;
+ }
+ ptr = ptr->next;
+ }
+ return NULL;
+}
+
+/*******
+ * This function sets the replication heartbeat
+ * into the maxscale_schema.replication_heartbeat table in the current master.
+ * The inserted values will be seen from all slaves replication from this master.
+ *
+ * @param handle The monitor handle
+ * @param database The number database server
+ */
+static void set_master_heartbeat(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *database)
+{
+ unsigned long id = handle->id;
+ time_t heartbeat;
+ time_t purge_time;
+ char heartbeat_insert_query[512] = "";
+ char heartbeat_purge_query[512] = "";
+ MYSQL_RES *result;
+ long returned_rows;
+
+ if (handle->master == NULL)
+ {
+ MXS_ERROR("set_master_heartbeat called without an available Master server");
+ return;
+ }
+
+ /* check if the maxscale_schema database and replication_heartbeat table exist */
+ if (mxs_mysql_query(database->con, "SELECT table_name FROM information_schema.tables "
+ "WHERE table_schema = 'maxscale_schema' AND table_name = 'replication_heartbeat'"))
+ {
+ MXS_ERROR( "Error checking for replication_heartbeat in Master server"
+ ": %s", mysql_error(database->con));
+ database->server->rlag = MAX_RLAG_NOT_AVAILABLE;
+ }
+
+ result = mysql_store_result(database->con);
+
+ if (result == NULL)
+ {
+ returned_rows = 0;
+ }
+ else
+ {
+ returned_rows = mysql_num_rows(result);
+ mysql_free_result(result);
+ }
+
+ if (0 == returned_rows)
+ {
+ /* create repl_heartbeat table in maxscale_schema database */
+ if (mxs_mysql_query(database->con, "CREATE TABLE IF NOT EXISTS "
+ "maxscale_schema.replication_heartbeat "
+ "(maxscale_id INT NOT NULL, "
+ "master_server_id INT NOT NULL, "
+ "master_timestamp INT UNSIGNED NOT NULL, "
+ "PRIMARY KEY ( master_server_id, maxscale_id ) ) "
+ "ENGINE=MYISAM DEFAULT CHARSET=latin1"))
+ {
+ MXS_ERROR("Error creating maxscale_schema.replication_heartbeat "
+ "table in Master server: %s", mysql_error(database->con));
+
+ database->server->rlag = MAX_RLAG_NOT_AVAILABLE;
+ }
+ }
+
+ /* auto purge old values after 48 hours*/
+ purge_time = time(0) - (3600 * 48);
+
+ sprintf(heartbeat_purge_query,
+ "DELETE FROM maxscale_schema.replication_heartbeat WHERE master_timestamp < %lu", purge_time);
+
+ if (mxs_mysql_query(database->con, heartbeat_purge_query))
+ {
+ MXS_ERROR("Error deleting from maxscale_schema.replication_heartbeat "
+ "table: [%s], %s",
+ heartbeat_purge_query,
+ mysql_error(database->con));
+ }
+
+ heartbeat = time(0);
+
+ /* set node_ts for master as time(0) */
+ database->server->node_ts = heartbeat;
+
+ sprintf(heartbeat_insert_query,
+ "UPDATE maxscale_schema.replication_heartbeat SET master_timestamp = %lu WHERE master_server_id = %li AND maxscale_id = %lu",
+ heartbeat, handle->master->server->node_id, id);
+
+ /* Try to insert MaxScale timestamp into master */
+ if (mxs_mysql_query(database->con, heartbeat_insert_query))
+ {
+
+ database->server->rlag = MAX_RLAG_NOT_AVAILABLE;
+
+ MXS_ERROR("Error updating maxscale_schema.replication_heartbeat table: [%s], %s",
+ heartbeat_insert_query,
+ mysql_error(database->con));
+ }
+ else
+ {
+ if (mysql_affected_rows(database->con) == 0)
+ {
+ heartbeat = time(0);
+ sprintf(heartbeat_insert_query,
+ "REPLACE INTO maxscale_schema.replication_heartbeat (master_server_id, maxscale_id, master_timestamp ) VALUES ( %li, %lu, %lu)",
+ handle->master->server->node_id, id, heartbeat);
+
+ if (mxs_mysql_query(database->con, heartbeat_insert_query))
+ {
+
+ database->server->rlag = MAX_RLAG_NOT_AVAILABLE;
+
+ MXS_ERROR("Error inserting into "
+ "maxscale_schema.replication_heartbeat table: [%s], %s",
+ heartbeat_insert_query,
+ mysql_error(database->con));
+ }
+ else
+ {
+ /* Set replication lag to 0 for the master */
+ database->server->rlag = 0;
+
+ MXS_DEBUG("heartbeat table inserted data for %s:%i",
+ database->server->name, database->server->port);
+ }
+ }
+ else
+ {
+ /* Set replication lag as 0 for the master */
+ database->server->rlag = 0;
+
+ MXS_DEBUG("heartbeat table updated for Master %s:%i",
+ database->server->name, database->server->port);
+ }
+ }
+}
+
+/*******
+ * This function gets the replication heartbeat
+ * from the maxscale_schema.replication_heartbeat table in the current slave
+ * and stores the timestamp and replication lag in the slave server struct
+ *
+ * @param handle The monitor handle
+ * @param database The number database server
+ */
+static void set_slave_heartbeat(MXS_MONITOR* mon, MXS_MONITOR_SERVERS *database)
+{
+ MYSQL_MONITOR *handle = (MYSQL_MONITOR*) mon->handle;
+ unsigned long id = handle->id;
+ time_t heartbeat;
+ char select_heartbeat_query[256] = "";
+ MYSQL_ROW row;
+ MYSQL_RES *result;
+
+ if (handle->master == NULL)
+ {
+ MXS_ERROR("set_slave_heartbeat called without an available Master server");
+ return;
+ }
+
+ /* Get the master_timestamp value from maxscale_schema.replication_heartbeat table */
+
+ sprintf(select_heartbeat_query, "SELECT master_timestamp "
+ "FROM maxscale_schema.replication_heartbeat "
+ "WHERE maxscale_id = %lu AND master_server_id = %li",
+ id, handle->master->server->node_id);
+
+ /* if there is a master then send the query to the slave with master_id */
+ if (handle->master != NULL && (mxs_mysql_query(database->con, select_heartbeat_query) == 0
+ && (result = mysql_store_result(database->con)) != NULL))
+ {
+ int rows_found = 0;
+
+ while ((row = mysql_fetch_row(result)))
+ {
+ int rlag = MAX_RLAG_NOT_AVAILABLE;
+ time_t slave_read;
+
+ rows_found = 1;
+
+ heartbeat = time(0);
+ slave_read = strtoul(row[0], NULL, 10);
+
+ if ((errno == ERANGE && (slave_read == LONG_MAX || slave_read == LONG_MIN)) || (errno != 0 &&
+ slave_read == 0))
+ {
+ slave_read = 0;
+ }
+
+ if (slave_read)
+ {
+ /* set the replication lag */
+ rlag = heartbeat - slave_read;
+ }
+
+ /* set this node_ts as master_timestamp read from replication_heartbeat table */
+ database->server->node_ts = slave_read;
+
+ if (rlag >= 0)
+ {
+ /* store rlag only if greater than monitor sampling interval */
+ database->server->rlag = ((unsigned int)rlag > (mon->interval / 1000)) ? rlag : 0;
+ }
+ else
+ {
+ database->server->rlag = MAX_RLAG_NOT_AVAILABLE;
+ }
+
+ MXS_DEBUG("Slave %s:%i has %i seconds lag",
+ database->server->name,
+ database->server->port,
+ database->server->rlag);
+ }
+ if (!rows_found)
+ {
+ database->server->rlag = MAX_RLAG_NOT_AVAILABLE;
+ database->server->node_ts = 0;
+ }
+
+ mysql_free_result(result);
+ }
+ else
+ {
+ database->server->rlag = MAX_RLAG_NOT_AVAILABLE;
+ database->server->node_ts = 0;
+
+ if (handle->master->server->node_id < 0)
+ {
+ MXS_ERROR("error: replication heartbeat: "
+ "master_server_id NOT available for %s:%i",
+ database->server->name,
+ database->server->port);
+ }
+ else
+ {
+ MXS_ERROR("error: replication heartbeat: "
+ "failed selecting from hearthbeat table of %s:%i : [%s], %s",
+ database->server->name,
+ database->server->port,
+ select_heartbeat_query,
+ mysql_error(database->con));
+ }
+ }
+}
+
+/*******
+ * This function computes the replication tree
+ * from a set of MySQL Master/Slave monitored servers
+ * and returns the root server with SERVER_MASTER bit.
+ * The tree is computed even for servers in 'maintenance' mode.
+ *
+ * @param handle The monitor handle
+ * @param num_servers The number of servers monitored
+ * @return The server at root level with SERVER_MASTER bit
+ */
+
+static MXS_MONITOR_SERVERS *get_replication_tree(MXS_MONITOR *mon, int num_servers)
+{
+ MYSQL_MONITOR* handle = (MYSQL_MONITOR*) mon->handle;
+ MXS_MONITOR_SERVERS *ptr;
+ MXS_MONITOR_SERVERS *backend;
+ SERVER *current;
+ int depth = 0;
+ long node_id;
+ int root_level;
+
+ ptr = mon->databases;
+ root_level = num_servers;
+
+ while (ptr)
+ {
+ /* The server could be in SERVER_IN_MAINT
+ * that means SERVER_IS_RUNNING returns 0
+ * Let's check only for SERVER_IS_DOWN: server is not running
+ */
+ if (SERVER_IS_DOWN(ptr->server))
+ {
+ ptr = ptr->next;
+ continue;
+ }
+ depth = 0;
+ current = ptr->server;
+
+ node_id = current->master_id;
+ if (node_id < 1)
+ {
+ MXS_MONITOR_SERVERS *find_slave;
+ find_slave = getSlaveOfNodeId(mon->databases, current->node_id);
+
+ if (find_slave == NULL)
+ {
+ current->depth = -1;
+ ptr = ptr->next;
+
+ continue;
+ }
+ else
+ {
+ current->depth = 0;
+ }
+ }
+ else
+ {
+ depth++;
+ }
+
+ while (depth <= num_servers)
+ {
+ /* set the root master at lowest depth level */
+ if (current->depth > -1 && current->depth < root_level)
+ {
+ root_level = current->depth;
+ handle->master = ptr;
+ }
+ backend = getServerByNodeId(mon->databases, node_id);
+
+ if (backend)
+ {
+ node_id = backend->server->master_id;
+ }
+ else
+ {
+ node_id = -1;
+ }
+
+ if (node_id > 0)
+ {
+ current->depth = depth + 1;
+ depth++;
+
+ }
+ else
+ {
+ MXS_MONITOR_SERVERS *master;
+ current->depth = depth;
+
+ master = getServerByNodeId(mon->databases, current->master_id);
+ if (master && master->server && master->server->node_id > 0)
+ {
+ add_slave_to_master(master->server->slaves, sizeof(master->server->slaves),
+ current->node_id);
+ master->server->depth = current->depth - 1;
+
+ if (handle->master && master->server->depth < handle->master->server->depth)
+ {
+ /** A master with a lower depth was found, remove
+ the master status from the previous master. */
+ monitor_clear_pending_status(handle->master, SERVER_MASTER);
+ }
+
+ MYSQL_SERVER_INFO* info = hashtable_fetch(handle->server_info,
+ master->server->unique_name);
+ ss_dassert(info);
+
+ if (SERVER_IS_RUNNING(master->server))
+ {
+ /** Only set the Master status if read_only is disabled */
+ monitor_set_pending_status(master, info->read_only ? SERVER_SLAVE : SERVER_MASTER);
+ }
+
+ handle->master = master;
+ }
+ else
+ {
+ if (current->master_id > 0)
+ {
+ /* this server is slave of another server not in MaxScale configuration
+ * we cannot use it as a real slave.
+ */
+ monitor_set_pending_status(ptr, SERVER_SLAVE);
+ monitor_set_pending_status(ptr, SERVER_SLAVE_OF_EXTERNAL_MASTER);
+ }
+ }
+ break;
+ }
+
+ }
+
+ ptr = ptr->next;
+ }
+
+ /*
+ * Return the root master
+ */
+
+ if (handle->master != NULL)
+ {
+ /* If the root master is in MAINT, return NULL */
+ if (SERVER_IN_MAINT(handle->master->server))
+ {
+ return NULL;
+ }
+ else
+ {
+ return handle->master;
+ }
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/*******
+ * This function add a slave id into the slaves server field
+ * of its master server
+ *
+ * @param slaves_list The slave list array of the master server
+ * @param list_size The size of the slave list
+ * @param node_id The node_id of the slave to be inserted
+ * @return 1 for inserted value and 0 otherwise
+ */
+static int add_slave_to_master(long *slaves_list, int list_size, long node_id)
+{
+ for (int i = 0; i < list_size; i++)
+ {
+ if (slaves_list[i] == 0)
+ {
+ slaves_list[i] = node_id;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * 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(MXS_MONITOR_SERVERS* database)
+{
+ MYSQL_RES *result;
+ bool rval = true;
+
+ if (mxs_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))
+ {
+ MXS_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
+ {
+ MXS_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(MXS_MONITOR_SERVERS* database)
+{
+ MYSQL_RES *result;
+ bool rval = true;
+
+ if (mxs_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)
+ {
+ MXS_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
+ {
+ MXS_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(MXS_MONITOR_SERVERS* database)
+{
+ MYSQL_RES *result;
+ bool rval = true;
+
+ if (mxs_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)
+ {
+ MXS_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
+ {
+ MXS_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(MXS_MONITOR_SERVERS* database)
+{
+ MYSQL_RES *result;
+ bool rval = true;
+
+ if (mxs_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)
+ {
+ MXS_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
+ {
+ MXS_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(MXS_MONITOR *monitor)
+{
+ MXS_MONITOR_SERVERS* database = monitor->databases;
+ bool err = false;
+
+ while (database)
+ {
+ mxs_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)
+ {
+ MXS_WARNING("Problems were encountered when checking if '%s' is replicated. Make sure that "
+ "the table is replicated to all slaves.", hb_table_name);
+ }
+}
diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c
index faa779e1f..fbb2251d2 100644
--- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c
+++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c
@@ -301,7 +301,7 @@ bool is_error_response(GWBUF *buffer)
* @param dcb Backend DCB where authentication failed
* @param buffer Buffer containing the response from the backend
*/
-void log_error_response(DCB *dcb, GWBUF *buffer)
+static void handle_error_response(DCB *dcb, GWBUF *buffer)
{
uint8_t *data = (uint8_t*)GWBUF_DATA(buffer);
size_t len = MYSQL_GET_PAYLOAD_LEN(data);
@@ -326,6 +326,16 @@ void log_error_response(DCB *dcb, GWBUF *buffer)
server_set_status(dcb->server, SERVER_MAINT);
}
+ else if (errcode == ER_ACCESS_DENIED_ERROR ||
+ errcode == ER_DBACCESS_DENIED_ERROR ||
+ errcode == ER_ACCESS_DENIED_NO_PASSWORD_ERROR)
+ {
+ if (dcb->session->state != SESSION_STATE_DUMMY)
+ {
+ // Authentication failed, reload users
+ service_refresh_users(dcb->service);
+ }
+ }
}
/**
@@ -494,7 +504,7 @@ gw_read_backend_event(DCB *dcb)
{
/** The server responded with an error */
proto->protocol_auth_state = MXS_AUTH_STATE_FAILED;
- log_error_response(dcb, readbuf);
+ handle_error_response(dcb, readbuf);
}
if (proto->protocol_auth_state == MXS_AUTH_STATE_CONNECTED)
@@ -887,7 +897,7 @@ gw_read_and_write(DCB *dcb)
{
/** The COM_CHANGE USER failed, generate a fake hangup event to
* close the DCB and send an error to the client. */
- log_error_response(dcb, reply);
+ handle_error_response(dcb, reply);
}
else
{
diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.cc b/server/modules/protocol/MySQL/MySQLClient/mysql_client.cc
index 879a9d6d7..4ee98a481 100644
--- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.cc
+++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.cc
@@ -71,7 +71,6 @@ static void mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_
static int gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
static int gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
static int gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities);
-static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read);
static void gw_process_one_new_client(DCB *client_dcb);
static spec_com_res_t process_special_commands(DCB *client_dcb, GWBUF *read_buffer, int nbytes_read);
static spec_com_res_t handle_query_kill(DCB* dcb, GWBUF* read_buffer, spec_com_res_t current,
@@ -816,51 +815,31 @@ static bool process_client_commands(DCB* dcb, int bytes_available, GWBUF** buffe
int pktlen;
uint8_t cmd = (uint8_t)MXS_COM_QUERY; // Treat empty packets as COM_QUERY
- /**
- * Buffer has at least 5 bytes, the packet is in contiguous memory
- * and it's the first packet in the buffer.
- */
- if (offset == 0 && GWBUF_LENGTH(queue) >= MYSQL_HEADER_LEN + 1)
+ uint8_t packet_header[MYSQL_HEADER_LEN];
+
+ if (gwbuf_copy_data(queue, offset, MYSQL_HEADER_LEN, packet_header) != MYSQL_HEADER_LEN)
{
- uint8_t *data = (uint8_t*)GWBUF_DATA(queue);
- pktlen = gw_mysql_get_byte3(data);
- if (pktlen)
- {
- cmd = *(data + MYSQL_HEADER_LEN);
- }
+ ss_dassert(offset > 0);
+ queue = split_and_store(dcb, queue, offset);
+ break;
}
+
+ pktlen = gw_mysql_get_byte3(packet_header);
+
/**
- * We have more than one packet in the buffer or the first 5 bytes
- * of a packet are split across two buffers.
+ * Check if the packet is empty, and if not, if we have the command byte.
+ * If we an empty packet or have at least 5 bytes of data, we can start
+ * sending the data to the router.
*/
- else
+ if (pktlen && gwbuf_copy_data(queue, offset + MYSQL_HEADER_LEN, 1, &cmd) != 1)
{
- uint8_t packet_header[MYSQL_HEADER_LEN];
-
- if (gwbuf_copy_data(queue, offset, MYSQL_HEADER_LEN, packet_header) != MYSQL_HEADER_LEN)
+ if ((queue = split_and_store(dcb, queue, offset)) == NULL)
{
- ss_dassert(offset > 0);
- queue = split_and_store(dcb, queue, offset);
- break;
- }
-
- pktlen = gw_mysql_get_byte3(packet_header);
-
- /**
- * Check if the packet is empty, and if not, if we have the command byte.
- * If we an empty packet or have at least 5 bytes of data, we can start
- * sending the data to the router.
- */
- if (pktlen && gwbuf_copy_data(queue, MYSQL_HEADER_LEN, 1, &cmd) != 1)
- {
- if ((queue = split_and_store(dcb, queue, offset)) == NULL)
- {
- ss_dassert(bytes_available == MYSQL_HEADER_LEN);
- return false;
- }
- ss_dassert(offset > 0);
- break;
+ ss_dassert(bytes_available - offset == MYSQL_HEADER_LEN);
+ return false;
}
+ ss_dassert(offset > 0);
+ break;
}
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
@@ -978,25 +957,28 @@ gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
/** Ask what type of input the router/filter chain expects */
capabilities = service_get_capabilities(session->service);
- /** Update the current protocol command being executed */
- if (!process_client_commands(dcb, nbytes_read, &read_buffer))
- {
- return 0;
- }
-
- /** If the router requires statement input or we are still authenticating
- * we need to make sure that a complete SQL packet is read before continuing */
+ /** If the router requires statement input we need to make sure that
+ * a complete SQL packet is read before continuing. The current command
+ * that is tracked by the protocol module is updated in route_by_statement() */
if (rcap_type_required(capabilities, RCAP_TYPE_STMT_INPUT))
{
- if (nbytes_read < 3 || nbytes_read <
- (int)(MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4))
+ uint8_t pktlen[MYSQL_HEADER_LEN];
+ size_t n_copied = gwbuf_copy_data(read_buffer, 0, MYSQL_HEADER_LEN, pktlen);
+
+ if (n_copied != sizeof(pktlen) ||
+ (uint32_t)nbytes_read < MYSQL_GET_PAYLOAD_LEN(pktlen) + MYSQL_HEADER_LEN)
{
- dcb_readq_set(dcb, read_buffer);
+ dcb_readq_append(dcb, read_buffer);
return 0;
}
set_qc_mode(session, &read_buffer);
}
+ /** Update the current protocol command being executed */
+ else if (!process_client_commands(dcb, nbytes_read, &read_buffer))
+ {
+ return 0;
+ }
/** The query classifier classifies according to the service's server that has
* the smallest version number. */
@@ -1027,6 +1009,30 @@ gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
return rval;
}
+/**
+ * Check if a connection qualifies to be added into the persistent connection pool
+ *
+ * @param dcb The client DCB to check
+ */
+void check_pool_candidate(DCB* dcb)
+{
+ MXS_SESSION *session = dcb->session;
+ MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
+
+ if (proto->current_command == MXS_COM_QUIT)
+ {
+ /** The client is closing the connection. We know that this will be the
+ * last command the client sends so the backend connections are very likely
+ * to be in an idle state.
+ *
+ * If the client is pipelining the queries (i.e. sending N request as
+ * a batch and then expecting N responses) then it is possible that
+ * the backend connections are not idle when the COM_QUIT is received.
+ * In most cases we can assume that the connections are idle. */
+ session_qualify_for_pool(session);
+ }
+}
+
/**
* @brief Client read event, common processing after single statement handling
*
@@ -1047,25 +1053,10 @@ gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities)
/** Reset error handler when routing of the new query begins */
dcb->dcb_errhandle_called = false;
- if (proto->current_command == MXS_COM_QUIT)
- {
- /** The client is closing the connection. We know that this will be the
- * last command the client sends so the backend connections are very likely
- * to be in an idle state.
- *
- * If the client is pipelining the queries (i.e. sending N request as
- * a batch and then expecting N responses) then it is possible that
- * the backend connections are not idle when the COM_QUIT is received.
- * In most cases we can assume that the connections are idle. */
- session_qualify_for_pool(session);
- }
-
if (rcap_type_required(capabilities, RCAP_TYPE_STMT_INPUT))
{
/**
- * Feed each statement completely and separately
- * to router. The routing functions return 1 for
- * success or 0 for failure.
+ * Feed each statement completely and separately to router.
*/
return_code = route_by_statement(session, capabilities, &read_buffer) ? 0 : 1;
@@ -1080,9 +1071,10 @@ gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities)
}
else if (NULL != session->router_session || (rcap_type_required(capabilities, RCAP_TYPE_NO_RSESSION)))
{
- /** Feed whole packet to router, which will free it
- * and return 1 for success, 0 for failure
- */
+ /** Check if this connection qualifies for the connection pool */
+ check_pool_candidate(dcb);
+
+ /** Feed the whole buffer to the router */
return_code = MXS_SESSION_ROUTE_QUERY(session, read_buffer) ? 0 : 1;
}
/* else return_code is still 0 from when it was originally set */
@@ -1424,13 +1416,41 @@ static int gw_client_hangup_event(DCB *dcb)
goto retblock;
}
- modutil_send_mysql_err_packet(dcb, 0, 0, 1927, "08S01", "Connection killed by MaxScale");
+ if (!session_valid_for_pool(session))
+ {
+ // The client did not send a COM_QUIT packet
+ modutil_send_mysql_err_packet(dcb, 0, 0, 1927, "08S01", "Connection killed by MaxScale");
+ }
dcb_close(dcb);
retblock:
return 1;
}
+/**
+ * Update protocol tracking information for an individual statement
+ *
+ * @param dcb Client DCB
+ * @param buffer Buffer containing a single packet
+ */
+void update_current_command(DCB* dcb, GWBUF* buffer)
+{
+ MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
+ uint8_t cmd = (uint8_t)MXS_COM_QUERY;
+
+ /**
+ * As we are routing individual packets, we can extract the command byte here.
+ * Empty packets are treated as COM_QUERY packets by default.
+ */
+ gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, 1, &cmd);
+ proto->current_command = (mxs_mysql_cmd_t)cmd;
+
+ /**
+ * Now that we have the current command, we can check if this connection
+ * can be a candidate for the connection pool.
+ */
+ check_pool_candidate(dcb);
+}
/**
* Detect if buffer includes partial mysql packet or multiple packets.
@@ -1464,21 +1484,11 @@ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF
{
CHK_GWBUF(packetbuf);
- MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol;
- proto->current_command = (mxs_mysql_cmd_t)mxs_mysql_get_command(packetbuf);
-
/**
- * This means that buffer includes exactly one MySQL
- * statement.
- * backend func.write uses the information. MySQL backend
- * protocol, for example, stores the command identifier
- * to protocol structure. When some other thread reads
- * the corresponding response the command tells how to
- * handle response.
- *
- * Set it here instead of gw_read_client_event to make
- * sure it is set to each (MySQL) packet.
+ * Update the currently command being executed.
*/
+ update_current_command(session->client_dcb, packetbuf);
+
if (rcap_type_required(capabilities, RCAP_TYPE_CONTIGUOUS_INPUT))
{
if (!GWBUF_IS_CONTIGUOUS(packetbuf))
@@ -1567,52 +1577,6 @@ return_rc:
return rc;
}
-/**
- * if read queue existed appent read to it. if length of read buffer is less
- * than 3 or less than mysql packet then return. else copy mysql packets to
- * separate buffers from read buffer and continue. else if read queue didn't
- * exist, length of read is less than 3 or less than mysql packet then
- * create read queue and append to it and return. if length read is less than
- * mysql packet length append to read queue append to it and return.
- * else (complete packet was read) continue.
- *
- * @return True if we have a complete packet, otherwise false
- */
-static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read)
-{
- if (dcb_readq_has(dcb))
- {
- dcb_readq_append(dcb, *read_buffer);
- nbytes_read = dcb_readq_length(dcb);
- int plen = MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(dcb_readq_get(dcb)));
-
- if (nbytes_read < 3 || nbytes_read < plen + 4)
- {
- return false;
- }
- else
- {
- /**
- * There is at least one complete mysql packet in
- * read_buffer.
- */
- *read_buffer = dcb_readq_release(dcb);
- }
- }
- else
- {
- uint8_t* data = (uint8_t *) GWBUF_DATA(*read_buffer);
-
- if (nbytes_read < 3 || nbytes_read < (int)MYSQL_GET_PAYLOAD_LEN(data) + 4)
- {
- dcb_readq_append(dcb, *read_buffer);
- return false;
- }
- }
-
- return true;
-}
-
/**
* Some SQL commands/queries need to be detected and handled by the protocol
* and MaxScale instead of being routed forward as is.
diff --git a/server/modules/routing/avrorouter/avro_file.c b/server/modules/routing/avrorouter/avro_file.c
index 68307054a..f497b0ac1 100644
--- a/server/modules/routing/avrorouter/avro_file.c
+++ b/server/modules/routing/avrorouter/avro_file.c
@@ -657,6 +657,13 @@ avro_binlog_end_t avro_read_all_events(AVRO_INSTANCE *router)
snprintf(next_file, sizeof(next_file), BINLOG_NAMEFMT, router->fileroot,
blr_file_get_next_binlogname(router->binlog_name));
}
+ else if (hdr.event_type == MARIADB_ANNOTATE_ROWS_EVENT)
+ {
+ MXS_INFO("Annotate_rows_event: %.*s", hdr.event_size - BINLOG_EVENT_HDR_LEN, ptr);
+ pos += original_size;
+ router->current_pos = pos;
+ continue;
+ }
else if (hdr.event_type == TABLE_MAP_EVENT)
{
handle_table_map_event(router, &hdr, ptr);
@@ -956,6 +963,8 @@ bool save_and_replace_table_create(AVRO_INSTANCE *router, TABLE_CREATE *created)
{
if (strcmp(key, table_ident) == 0)
{
+ TABLE_MAP* map = hashtable_fetch(router->table_maps, key);
+ router->active_maps[map->id % MAX_MAPPED_TABLES] = NULL;
hashtable_delete(router->table_maps, key);
}
}
@@ -1000,13 +1009,13 @@ void handle_query_event(AVRO_INSTANCE *router, REP_HEADER *hdr, int *pending_tra
memcpy(db, (char*) ptr + PHDR_OFF + vblklen, dblen);
db[dblen] = 0;
- unify_whitespace(sql, len);
size_t sqlsz = len, tmpsz = len;
char *tmp = MXS_MALLOC(len);
MXS_ABORT_IF_NULL(tmp);
remove_mysql_comments((const char**)&sql, &sqlsz, &tmp, &tmpsz);
sql = tmp;
len = tmpsz;
+ unify_whitespace(sql, len);
if (is_create_table_statement(router, sql, len))
{
diff --git a/server/modules/routing/avrorouter/avro_rbr.c b/server/modules/routing/avrorouter/avro_rbr.c
index d34820023..89f0e4182 100644
--- a/server/modules/routing/avrorouter/avro_rbr.c
+++ b/server/modules/routing/avrorouter/avro_rbr.c
@@ -104,72 +104,53 @@ bool handle_table_map_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr
{
ss_dassert(create->columns > 0);
TABLE_MAP *old = hashtable_fetch(router->table_maps, table_ident);
+ TABLE_MAP *map = table_map_alloc(ptr, ev_len, create);
+ MXS_ABORT_IF_NULL(map); // Fatal error at this point
+ char* json_schema = json_new_schema_from_table(map);
- if (old == NULL || old->version != create->version)
+ if (json_schema)
{
- TABLE_MAP *map = table_map_alloc(ptr, ev_len, create);
+ char filepath[PATH_MAX + 1];
+ snprintf(filepath, sizeof(filepath), "%s/%s.%06d.avro",
+ router->avrodir, table_ident, map->version);
- if (map)
+ /** Close the file and open a new one */
+ hashtable_delete(router->open_tables, table_ident);
+ AVRO_TABLE *avro_table = avro_table_alloc(filepath, json_schema,
+ codec_to_string(router->codec),
+ router->block_size);
+
+ if (avro_table)
{
- char* json_schema = json_new_schema_from_table(map);
+ bool notify = old != NULL;
- if (json_schema)
+ if (old)
{
- char filepath[PATH_MAX + 1];
- snprintf(filepath, sizeof(filepath), "%s/%s.%06d.avro",
- router->avrodir, table_ident, map->version);
-
- /** Close the file and open a new one */
- hashtable_delete(router->open_tables, table_ident);
- AVRO_TABLE *avro_table = avro_table_alloc(filepath, json_schema,
- codec_to_string(router->codec),
- router->block_size);
-
- if (avro_table)
- {
- bool notify = old != NULL;
-
- if (old)
- {
router->active_maps[old->id % MAX_MAPPED_TABLES] = NULL;
}
hashtable_delete(router->table_maps, table_ident);
- hashtable_add(router->table_maps, (void*) table_ident, map);
- hashtable_add(router->open_tables, table_ident, avro_table);
- save_avro_schema(router->avrodir, json_schema, map);
- router->active_maps[map->id % MAX_MAPPED_TABLES] = map;
- MXS_DEBUG("Table %s mapped to %lu", table_ident, map->id);
- rval = true;
+ hashtable_add(router->table_maps, (void*)table_ident, map);
+ hashtable_add(router->open_tables, table_ident, avro_table);
+ save_avro_schema(router->avrodir, json_schema, map);
+ router->active_maps[map->id % MAX_MAPPED_TABLES] = map;
+ ss_dassert(router->active_maps[id % MAX_MAPPED_TABLES] == map);
+ MXS_DEBUG("Table %s mapped to %lu", table_ident, map->id);
+ rval = true;
- if (notify)
- {
- notify_all_clients(router);
- }
- }
- else
- {
- MXS_ERROR("Failed to open new Avro file for writing.");
- }
- MXS_FREE(json_schema);
- }
- else
+ if (notify)
{
- MXS_ERROR("Failed to create JSON schema.");
+ notify_all_clients(router);
}
}
else
{
- MXS_ERROR("Failed to allocate new table map.");
+ MXS_ERROR("Failed to open new Avro file for writing.");
}
+ MXS_FREE(json_schema);
}
else
{
- router->active_maps[old->id % MAX_MAPPED_TABLES] = NULL;
- table_map_remap(ptr, ev_len, old);
- router->active_maps[old->id % MAX_MAPPED_TABLES] = old;
- MXS_DEBUG("Table %s re-mapped to %lu", table_ident, old->id);
- /** No changes in the schema */
- rval = true;
+ MXS_ERROR("Failed to create JSON schema.");
}
}
else
@@ -363,8 +344,9 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr)
}
else
{
- MXS_ERROR("Row event and table map event have different column counts."
- " Only full row image is currently supported.");
+ MXS_ERROR("Row event and table map event have different column "
+ "counts for table %s.%s, only full row image is currently "
+ "supported.", map->database, map->table);
}
}
else
@@ -606,7 +588,6 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value
}
MXS_INFO("[%ld] CHAR: field: %d bytes, data: %d bytes", i, field_length, bytes);
- ss_dassert(bytes || *ptr == '\0');
char str[bytes + 1];
memcpy(str, ptr, bytes);
str[bytes] = '\0';
diff --git a/server/modules/routing/avrorouter/avro_schema.c b/server/modules/routing/avrorouter/avro_schema.c
index e68dd3434..95c6f5534 100644
--- a/server/modules/routing/avrorouter/avro_schema.c
+++ b/server/modules/routing/avrorouter/avro_schema.c
@@ -321,11 +321,11 @@ void save_avro_schema(const char *path, const char* schema, TABLE_MAP *map)
* @return Pointer to the start of the definition of NULL if the query is
* malformed.
*/
-static const char* get_table_definition(const char *sql, int* size)
+static const char* get_table_definition(const char *sql, int len, int* size)
{
const char *rval = NULL;
const char *ptr = sql;
- const char *end = strchr(sql, '\0');
+ const char *end = sql + len;
while (ptr < end && *ptr != '(')
{
ptr++;
@@ -403,10 +403,12 @@ static bool get_table_name(const char* sql, char* dest)
/**
* Extract the database name from a CREATE TABLE statement
+ *
* @param sql SQL statement
* @param dest Destination where the database name is extracted. Must be at least
- * MYSQL_DATABASE_MAXLEN bytes long.
- * @return True if extraction was successful
+ * MYSQL_DATABASE_MAXLEN bytes long.
+ *
+ * @return True if a database name was extracted
*/
static bool get_database_name(const char* sql, char* dest)
{
@@ -426,22 +428,27 @@ static bool get_database_name(const char* sql, char* dest)
ptr--;
}
- while (*ptr == '`' || *ptr == '.' || isspace(*ptr))
+ if (*ptr == '.')
{
- ptr--;
+ // The query defines an explicit database
+
+ while (*ptr == '`' || *ptr == '.' || isspace(*ptr))
+ {
+ ptr--;
+ }
+
+ const char* end = ptr + 1;
+
+ while (*ptr != '`' && *ptr != '.' && !isspace(*ptr))
+ {
+ ptr--;
+ }
+
+ ptr++;
+ memcpy(dest, ptr, end - ptr);
+ dest[end - ptr] = '\0';
+ rval = true;
}
-
- const char* end = ptr + 1;
-
- while (*ptr != '`' && *ptr != '.' && !isspace(*ptr))
- {
- ptr--;
- }
-
- ptr++;
- memcpy(dest, ptr, end - ptr);
- dest[end - ptr] = '\0';
- rval = true;
}
return rval;
@@ -512,12 +519,16 @@ static const char *extract_field_name(const char* ptr, char* dest, size_t size)
}
}
- if (strncasecmp(ptr, "constraint", 10) == 0 || strncasecmp(ptr, "index", 5) == 0 ||
- strncasecmp(ptr, "key", 3) == 0 || strncasecmp(ptr, "fulltext", 8) == 0 ||
- strncasecmp(ptr, "spatial", 7) == 0 || strncasecmp(ptr, "foreign", 7) == 0 ||
- strncasecmp(ptr, "unique", 6) == 0 || strncasecmp(ptr, "primary", 7) == 0)
+ if (!bt)
{
- return NULL;
+ if (strncasecmp(ptr, "constraint", 10) == 0 || strncasecmp(ptr, "index", 5) == 0 ||
+ strncasecmp(ptr, "key", 3) == 0 || strncasecmp(ptr, "fulltext", 8) == 0 ||
+ strncasecmp(ptr, "spatial", 7) == 0 || strncasecmp(ptr, "foreign", 7) == 0 ||
+ strncasecmp(ptr, "unique", 6) == 0 || strncasecmp(ptr, "primary", 7) == 0)
+ {
+ // Found a keyword
+ return NULL;
+ }
}
const char *start = ptr;
@@ -694,35 +705,42 @@ TABLE_CREATE* table_create_from_schema(const char* file, const char* db,
* @param db Database where this query was executed
* @return New CREATE_TABLE object or NULL if an error occurred
*/
-TABLE_CREATE* table_create_alloc(const char* sql, int len, const char* event_db)
+TABLE_CREATE* table_create_alloc(const char* sql, int len, const char* db)
{
/** Extract the table definition so we can get the column names from it */
int stmt_len = 0;
- const char* statement_sql = get_table_definition(sql, &stmt_len);
+ const char* statement_sql = get_table_definition(sql, len, &stmt_len);
ss_dassert(statement_sql);
char table[MYSQL_TABLE_MAXLEN + 1];
char database[MYSQL_DATABASE_MAXLEN + 1];
- const char *db = event_db;
-
+ const char* err = NULL;
MXS_INFO("Create table: %.*s", len, sql);
- if (!get_table_name(sql, table))
+ if (!statement_sql)
{
- MXS_ERROR("Malformed CREATE TABLE statement, could not extract table name: %s", sql);
- return NULL;
+ err = "table definition";
+ }
+ else if (!get_table_name(sql, table))
+ {
+ err = "table name";
}
- /** The CREATE statement contains the database name */
- if (strlen(db) == 0)
+ if (get_database_name(sql, database))
{
- if (!get_database_name(sql, database))
- {
- MXS_ERROR("Malformed CREATE TABLE statement, could not extract "
- "database name: %s", sql);
- return NULL;
- }
+ // The CREATE statement contains the database name
db = database;
}
+ else if (*db == '\0')
+ {
+ // No explicit or current database
+ err = "database name";
+ }
+
+ if (err)
+ {
+ MXS_ERROR("Malformed CREATE TABLE statement, could not extract %s: %.*s", err, len, sql);
+ return NULL;
+ }
int* lengths = NULL;
char **names = NULL;
@@ -893,6 +911,27 @@ static void remove_extras(char* str)
ss_dassert(strlen(str) == len);
}
+
+static void remove_backticks(char* src)
+{
+ char* dest = src;
+
+ while (*src)
+ {
+ if (*src != '`')
+ {
+ // Non-backtick character, keep it
+ *dest = *src;
+ dest++;
+ }
+
+ src++;
+ }
+
+ ss_dassert(dest == src || (*dest != '\0' && dest < src));
+ *dest = '\0';
+}
+
/**
* Extract both tables from a `CREATE TABLE t1 LIKE t2` statement
*/
@@ -1095,10 +1134,12 @@ static bool tok_eq(const char *a, const char *b, size_t len)
void read_alter_identifier(const char *sql, const char *end, char *dest, int size)
{
int len = 0;
- const char *tok = get_tok(sql, &len, end);
- if (tok && (tok = get_tok(tok + len, &len, end)) && (tok = get_tok(tok + len, &len, end)))
+ const char *tok = get_tok(sql, &len, end); // ALTER
+ if (tok && (tok = get_tok(tok + len, &len, end)) // TABLE
+ && (tok = get_tok(tok + len, &len, end))) // Table identifier
{
snprintf(dest, size, "%.*s", len, tok);
+ remove_backticks(dest);
}
}
@@ -1174,20 +1215,33 @@ bool table_create_alter(TABLE_CREATE *create, const char *sql, const char *end)
if (tok_eq(ptok, "add", plen) && tok_eq(tok, "column", len))
{
tok = get_tok(tok + len, &len, end);
-
- create->column_names = MXS_REALLOC(create->column_names, sizeof(char*) * (create->columns + 1));
- create->column_types = MXS_REALLOC(create->column_types, sizeof(char*) * (create->columns + 1));
- create->column_lengths = MXS_REALLOC(create->column_lengths, sizeof(int) * (create->columns + 1));
-
char avro_token[len + 1];
make_avro_token(avro_token, tok, len);
- char field_type[200] = ""; // Enough to hold all types
- int field_length = extract_type_length(tok + len, field_type);
- create->column_names[create->columns] = MXS_STRDUP_A(avro_token);
- create->column_types[create->columns] = MXS_STRDUP_A(field_type);
- create->column_lengths[create->columns] = field_length;
- create->columns++;
- updates++;
+ bool is_new = true;
+
+ for (uint64_t i = 0; i < create->columns; i++)
+ {
+ if (strcmp(avro_token, create->column_names[i]) == 0)
+ {
+ is_new = false;
+ break;
+ }
+ }
+
+ if (is_new)
+ {
+ create->column_names = MXS_REALLOC(create->column_names, sizeof(char*) * (create->columns + 1));
+ create->column_types = MXS_REALLOC(create->column_types, sizeof(char*) * (create->columns + 1));
+ create->column_lengths = MXS_REALLOC(create->column_lengths, sizeof(int) * (create->columns + 1));
+
+ char field_type[200] = ""; // Enough to hold all types
+ int field_length = extract_type_length(tok + len, field_type);
+ create->column_names[create->columns] = MXS_STRDUP_A(avro_token);
+ create->column_types[create->columns] = MXS_STRDUP_A(field_type);
+ create->column_lengths[create->columns] = field_length;
+ create->columns++;
+ updates++;
+ }
tok = get_next_def(tok, end);
len = 0;
}
@@ -1393,20 +1447,3 @@ void table_map_free(TABLE_MAP *map)
MXS_FREE(map);
}
}
-
-/**
- * @brief Map a table to a different ID
- *
- * This updates the table ID that the @c TABLE_MAP object is assigned with
- *
- * @param ptr Pointer to the start of a table map event
- * @param hdr_len Post-header length
- * @param map Table map to remap
- */
-void table_map_remap(uint8_t *ptr, uint8_t hdr_len, TABLE_MAP *map)
-{
- uint64_t table_id = 0;
- size_t id_size = hdr_len == 6 ? 4 : 6;
- memcpy(&table_id, ptr, id_size);
- map->id = table_id;
-}
diff --git a/server/modules/routing/avrorouter/avrorouter.h b/server/modules/routing/avrorouter/avrorouter.h
index 50e5e2c63..27818068c 100644
--- a/server/modules/routing/avrorouter/avrorouter.h
+++ b/server/modules/routing/avrorouter/avrorouter.h
@@ -328,7 +328,6 @@ extern char* json_new_schema_from_table(TABLE_MAP *map);
extern void save_avro_schema(const char *path, const char* schema, TABLE_MAP *map);
extern bool handle_table_map_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr);
extern bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr);
-extern void table_map_remap(uint8_t *ptr, uint8_t hdr_len, TABLE_MAP *map);
enum avrorouter_file_op
{
diff --git a/server/modules/routing/binlogrouter/blr_master.c b/server/modules/routing/binlogrouter/blr_master.c
index 04a8ced21..6a57036ea 100644
--- a/server/modules/routing/binlogrouter/blr_master.c
+++ b/server/modules/routing/binlogrouter/blr_master.c
@@ -229,6 +229,7 @@ static void blr_start_master(void* data)
return;
}
client->session = router->session;
+ client->service = router->service;
/**
* 'client' is the fake DCB that emulates a client session:
@@ -265,6 +266,7 @@ static void blr_start_master(void* data)
return;
}
router->master->remote = MXS_STRDUP_A(router->service->dbref->server->name);
+ router->master->service = router->service;
MXS_NOTICE("%s: attempting to connect to master"
" server [%s]:%d, binlog='%s', pos=%lu%s%s",