
In this context wsrep_gtid_domain_id and gtid_domain_id need to be the same to ensure that all gtids will be the same.
564 lines
16 KiB
C++
564 lines
16 KiB
C++
/*
|
|
* 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: 2022-01-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.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <sstream>
|
|
#include <maxbase/assert.h>
|
|
#include <maxbase/log.hh>
|
|
#include "testconnections.h"
|
|
|
|
using namespace std;
|
|
|
|
// This test checks that BLR replication from a Galera cluster works if
|
|
// - all servers in the Galera cluster have @@log_slave_updates on,
|
|
// - all servers in the Galera cluster have the same server id, and
|
|
// - even if updates are made in every node of the cluster.
|
|
//
|
|
//
|
|
// By default that will not work as BLR stores the binlog file in a directory
|
|
// named according to the server id *and* later assumes that the directory
|
|
// can be deduced from the GTID. That is an erroneous assumption, as the GTID
|
|
// of events generated in a Galera cluster contain the server id of the node
|
|
// where the write was generated, not the server id of the node from which
|
|
// BLR replicates.
|
|
|
|
namespace
|
|
{
|
|
|
|
enum class Approach
|
|
{
|
|
GTID,
|
|
FILE_POS
|
|
};
|
|
|
|
void test_sleep(int seconds)
|
|
{
|
|
cout << "Sleeping " << seconds << " seconds: " << flush;
|
|
while (seconds)
|
|
{
|
|
cout << "." << flush;
|
|
sleep(1);
|
|
--seconds;
|
|
}
|
|
cout << endl;
|
|
}
|
|
|
|
// The amount of time slept between various operations that are
|
|
// expected to take some time before becoming visible.
|
|
|
|
const int HEARTBEAT_PERIOD = 2; // Seconds
|
|
const int REPLICATION_SLEEP = 6; // Seconds
|
|
|
|
string get_gtid_current_pos(TestConnections& test, MYSQL* pMysql)
|
|
{
|
|
std::vector<string> row = get_row(pMysql, "SELECT @@gtid_current_pos");
|
|
|
|
test.expect(row.size() == 1, "Did not get @@gtid_current_pos");
|
|
|
|
return row.empty() ? "" : row[0];
|
|
}
|
|
|
|
string get_server_id(TestConnections& test, MYSQL* pMysql)
|
|
{
|
|
std::vector<string> row = get_row(pMysql, "SELECT @@server_id");
|
|
|
|
test.expect(row.size() == 1, "Did not get @@server_id");
|
|
|
|
return row.empty() ? "" : row[0];
|
|
}
|
|
|
|
bool setup_secondary_masters(TestConnections& test, MYSQL* pMaxscale)
|
|
{
|
|
test.try_query(pMaxscale, "STOP SLAVE");
|
|
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
for (int i = 1; i < gc.N; ++i)
|
|
{
|
|
stringstream ss;
|
|
|
|
ss << "CHANGE MASTER ':" << i + 1 << "' ";
|
|
ss << "TO MASTER_HOST='" << gc.IP[i] << "', ";
|
|
ss << "MASTER_PORT=" << gc.port[i];
|
|
|
|
string stmt = ss.str();
|
|
|
|
cout << stmt << endl;
|
|
|
|
test.try_query(pMaxscale, "%s", stmt.c_str());
|
|
}
|
|
|
|
test.try_query(pMaxscale, "START SLAVE");
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
// Setup BLR to replicate from galera_000.
|
|
bool setup_blr(TestConnections& test, MYSQL* pMaxscale, const string& gtid, Approach approach)
|
|
{
|
|
test.tprintf("Setting up BLR");
|
|
|
|
test.try_query(pMaxscale, "STOP SLAVE");
|
|
if (approach == Approach::GTID)
|
|
{
|
|
test.try_query(pMaxscale, "SET @@global.gtid_slave_pos='%s'", gtid.c_str());
|
|
}
|
|
|
|
mxb_assert(test.galera);
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
stringstream ss;
|
|
|
|
ss << "CHANGE MASTER ";
|
|
ss << "TO MASTER_HOST='" << gc.IP[0] << "'";
|
|
ss << ", MASTER_PORT=" << gc.port[0];
|
|
ss << ", MASTER_USER='repl', MASTER_PASSWORD='repl'";
|
|
if (approach == Approach::GTID)
|
|
{
|
|
ss << ", MASTER_USE_GTID=Slave_pos";
|
|
}
|
|
else
|
|
{
|
|
ss << ", MASTER_LOG_FILE='galera-cluster.000001'";
|
|
}
|
|
ss << ", MASTER_HEARTBEAT_PERIOD=" << HEARTBEAT_PERIOD;
|
|
|
|
string stmt = ss.str();
|
|
|
|
cout << stmt << endl;
|
|
|
|
test.try_query(pMaxscale, "%s", stmt.c_str());
|
|
test.try_query(pMaxscale, "START SLAVE");
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
// Setup slave to replicate from BLR.
|
|
bool setup_slave(TestConnections& test,
|
|
const string& gtid,
|
|
MYSQL* pSlave,
|
|
const char* zMaxscale_host,
|
|
int maxscale_port,
|
|
Approach approach)
|
|
{
|
|
test.tprintf("Setting up Slave");
|
|
|
|
test.try_query(pSlave, "STOP SLAVE");
|
|
test.try_query(pSlave, "RESET SLAVE");
|
|
test.try_query(pSlave, "DROP TABLE IF EXISTS test.MXS1980");
|
|
if (approach == Approach::GTID)
|
|
{
|
|
test.try_query(pSlave, "SET @@global.gtid_slave_pos='%s'", gtid.c_str());
|
|
}
|
|
|
|
stringstream ss;
|
|
|
|
ss << "CHANGE MASTER TO ";
|
|
ss << "MASTER_HOST='" << zMaxscale_host << "'";
|
|
ss << ", MASTER_PORT=" << maxscale_port;
|
|
ss << ", MASTER_USER='repl', MASTER_PASSWORD='repl'";
|
|
if (approach == Approach::GTID)
|
|
{
|
|
ss << ", MASTER_USE_GTID=Slave_pos";
|
|
}
|
|
else
|
|
{
|
|
ss << ", MASTER_LOG_FILE='galera-cluster.000001'";
|
|
}
|
|
ss << ", MASTER_HEARTBEAT_PERIOD=" << HEARTBEAT_PERIOD;
|
|
|
|
string stmt = ss.str();
|
|
|
|
cout << stmt << endl;
|
|
|
|
test.try_query(pSlave, "%s", stmt.c_str());
|
|
test.try_query(pSlave, "START SLAVE");
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
bool setup_schema(TestConnections& test, MYSQL* pServer)
|
|
{
|
|
test.try_query(pServer, "DROP TABLE IF EXISTS test.MXS1980");
|
|
test.try_query(pServer, "CREATE TABLE test.MXS1980 (i INT)");
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
unsigned inserted_rows = 0;
|
|
|
|
void insert(TestConnections& test, MYSQL* pMaster)
|
|
{
|
|
stringstream ss;
|
|
ss << "INSERT INTO test.MXS1980 VALUES (" << ++inserted_rows << ")";
|
|
|
|
string stmt = ss.str();
|
|
|
|
cout << stmt.c_str() << endl;
|
|
|
|
test.try_query(pMaster, "%s", stmt.c_str());
|
|
}
|
|
|
|
void select(TestConnections& test, MYSQL* pSlave)
|
|
{
|
|
int attempts = 5;
|
|
|
|
my_ulonglong nRows = 0;
|
|
unsigned long long nResult_sets;
|
|
int rc;
|
|
|
|
do
|
|
{
|
|
--attempts;
|
|
|
|
rc = execute_query_num_of_rows(pSlave, "SELECT * FROM test.MXS1980", &nRows, &nResult_sets);
|
|
test.expect(rc == 0, "Execution of SELECT failed.");
|
|
|
|
if (rc == 0)
|
|
{
|
|
mxb_assert(nResult_sets == 1);
|
|
|
|
if (nRows != inserted_rows)
|
|
{
|
|
// If we don't get the expected result, we sleep a while and retry with the
|
|
// assumption that it's just a replication delay.
|
|
test_sleep(2);
|
|
}
|
|
}
|
|
}
|
|
while ((rc == 0) && (nRows != inserted_rows) && attempts);
|
|
|
|
test.expect(nRows == inserted_rows, "Expected %d rows, got %d.", inserted_rows, (int)nRows);
|
|
}
|
|
|
|
bool insert_select(TestConnections& test, MYSQL* pSlave, MYSQL* pMaster)
|
|
{
|
|
insert(test, pMaster);
|
|
test_sleep(REPLICATION_SLEEP); // To ensure that the insert reaches the slave.
|
|
select(test, pSlave);
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
bool insert_select(TestConnections& test, MYSQL* pSlave)
|
|
{
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
for (int i = 0; i < gc.N; ++i)
|
|
{
|
|
MYSQL* pMaster = gc.nodes[i];
|
|
|
|
insert_select(test, pSlave, pMaster);
|
|
}
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
void reset_galera(TestConnections& test)
|
|
{
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
for (int i = 0; i < gc.N; ++i)
|
|
{
|
|
test.try_query(gc.nodes[i], "RESET MASTER");
|
|
}
|
|
}
|
|
|
|
// Ensure log_slave_updates is on.
|
|
void setup_galera(TestConnections& test)
|
|
{
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
for (int i = 0; i < gc.N; ++i)
|
|
{
|
|
gc.stash_server_settings(i);
|
|
// https://mariadb.com/kb/en/library/using-mariadb-gtids-with-mariadb-galera-cluster/#wsrep-gtid-mode
|
|
gc.add_server_setting(i, "wsrep_gtid_mode=ON");
|
|
gc.add_server_setting(i, "wsrep_gtid_domain_id=0");
|
|
gc.add_server_setting(i, "gtid_domain_id=0");
|
|
gc.add_server_setting(i, "log_slave_updates=1");
|
|
gc.add_server_setting(i, "log_bin=galera-cluster");
|
|
}
|
|
}
|
|
|
|
// Restore log_slave_updates as it was.
|
|
void restore_galera(TestConnections& test)
|
|
{
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
for (int i = 0; i < gc.N; ++i)
|
|
{
|
|
gc.restore_server_settings(i);
|
|
}
|
|
|
|
int rc = gc.start_replication();
|
|
test.expect(rc == 0, "Could not start Galera cluster.");
|
|
}
|
|
|
|
bool setup_server_ids(TestConnections& test, map<int, string>* pServer_ids_by_index)
|
|
{
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
string common_server_id = get_server_id(test, gc.nodes[0]);
|
|
|
|
if (!common_server_id.empty())
|
|
{
|
|
test.tprintf("Setting server_id for all servers to %s.", common_server_id.c_str());
|
|
|
|
for (int i = 1; i < gc.N; ++i)
|
|
{
|
|
string server_id = get_server_id(test, gc.nodes[i]);
|
|
|
|
if (!server_id.empty())
|
|
{
|
|
test.tprintf("Changing id from %s to %s.", server_id.c_str(), common_server_id.c_str());
|
|
test.try_query(gc.nodes[i], "set GLOBAL server_id=%s", common_server_id.c_str());
|
|
pServer_ids_by_index->insert(std::make_pair(i, server_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
void restore_server_ids(TestConnections& test, const map<int, string>& server_ids_by_index)
|
|
{
|
|
for_each(server_ids_by_index.begin(),
|
|
server_ids_by_index.end(),
|
|
[&test](const pair<int, string>& server_id_by_index)
|
|
{
|
|
test.try_query(test.galera->nodes[server_id_by_index.first],
|
|
"set GLOBAL server_id=%s", server_id_by_index.second.c_str());
|
|
});
|
|
}
|
|
|
|
// STOP SLAVE; START SLAVE cycle.
|
|
void restart_slave(TestConnections& test, MYSQL* pSlave)
|
|
{
|
|
Row row;
|
|
|
|
auto replication_failed = [](const std::string & column)
|
|
{
|
|
return column.find("Got fatal error") != string::npos;
|
|
};
|
|
|
|
cout << "Stopping slave." << endl;
|
|
test.try_query(pSlave, "STOP SLAVE");
|
|
|
|
row = get_row(pSlave, "SHOW SLAVE STATUS");
|
|
auto it1 = std::find_if(row.begin(), row.end(), replication_failed);
|
|
test.expect(it1 == row.end(), "Replication failed.");
|
|
|
|
cout << "Starting slave." << endl;
|
|
test.try_query(pSlave, "START SLAVE");
|
|
|
|
test_sleep(REPLICATION_SLEEP);
|
|
|
|
// With the correct setup:
|
|
// - log_slave_updates is on,
|
|
// - all Galera nodes have the same server id,
|
|
// this should work.
|
|
|
|
row = get_row(pSlave, "SHOW SLAVE STATUS");
|
|
auto it2 = std::find_if(row.begin(), row.end(), replication_failed);
|
|
test.expect(it2 == row.end(), "START SLAVE failed.");
|
|
}
|
|
|
|
bool test_basics(TestConnections& test, MYSQL* pSlave)
|
|
{
|
|
if (insert_select(test, pSlave))
|
|
{
|
|
restart_slave(test, pSlave);
|
|
}
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
|
|
bool test_multiple_masters(TestConnections& test, MYSQL* pSlave)
|
|
{
|
|
Mariadb_nodes& gc = *test.galera;
|
|
|
|
for (int i = 0; i < gc.N; ++i)
|
|
{
|
|
test.tprintf("Blocking Galera node %d", i);
|
|
gc.block_node(i);
|
|
// Wait a number of times the hearbeat period so as to allow BLR
|
|
// enough time to detect the lack of the heartbeat and time
|
|
// to take corrective action.
|
|
test_sleep(5 * HEARTBEAT_PERIOD);
|
|
|
|
MYSQL* pMaster = gc.nodes[(i + 1) % gc.N];
|
|
|
|
insert_select(test, pSlave, pMaster);
|
|
|
|
test.tprintf("Unblocking Galera node %d", i);
|
|
gc.unblock_node(i);
|
|
}
|
|
|
|
return test.global_result == 0;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
mxb::Log log(MXB_LOG_TARGET_STDOUT);
|
|
|
|
TestConnections::require_galera(true);
|
|
TestConnections::skip_maxscale_start(true);
|
|
TestConnections test(argc, argv);
|
|
|
|
bool dont_setup_galera = getenv("MXS1980_DONT_SETUP_GALERA") ? true : false;
|
|
|
|
if (!dont_setup_galera)
|
|
{
|
|
setup_galera(test);
|
|
test.galera->start_replication(); // Causes restart.
|
|
}
|
|
|
|
const char* zValue;
|
|
|
|
// For debugging the test and functionality, allow the BLR host and port to be
|
|
// specified/ using environment variables.
|
|
zValue = getenv("MXS1980_BLR_HOST");
|
|
const char* zMaxscale_host = (zValue ? zValue : test.maxscales->IP[0]);
|
|
cout << "MaxScale host: " << zMaxscale_host << endl;
|
|
|
|
zValue = getenv("MXS1980_BLR_PORT");
|
|
int maxscale_port = (zValue ? atoi(zValue) : test.maxscales->binlog_port[0]);
|
|
cout << "MaxScale port: " << maxscale_port << endl;
|
|
|
|
Mariadb_nodes& gc = *test.galera;
|
|
gc.connect();
|
|
|
|
map<int, string> server_ids_by_index;
|
|
|
|
if (setup_server_ids(test, &server_ids_by_index))
|
|
{
|
|
for (Approach approach :
|
|
{
|
|
Approach::GTID, Approach::FILE_POS
|
|
})
|
|
{
|
|
inserted_rows = 0;
|
|
|
|
reset_galera(test);
|
|
|
|
test.stop_maxscale(0);
|
|
|
|
test.maxscales->ssh_node(0, "rm -f /var/lib/maxscale/master.ini", true);
|
|
test.maxscales->ssh_node(0, "rm -f /var/lib/maxscale/gtid_maps.db", true);
|
|
test.maxscales->ssh_node(0, "rm -rf /var/lib/maxscale/0", true);
|
|
|
|
if (approach == Approach::GTID)
|
|
{
|
|
cout << "\nRunning tests using GTID replication.\n" << endl;
|
|
test.add_result(test.maxscales->ssh_node(0, "sed -i -e 's/Off/On/' /etc/maxscale.cnf", true),
|
|
"Could not tweak /etc/maxscale.cnf");
|
|
}
|
|
else
|
|
{
|
|
cout << "\nRunning test using FILE + POS replication.\n" << endl;
|
|
test.add_result(test.maxscales->ssh_node(0, "sed -i -e 's/On/Off/' /etc/maxscale.cnf", true),
|
|
"Could not tweak /etc/maxscale.cnf");
|
|
}
|
|
|
|
test.start_maxscale(0);
|
|
|
|
string gtid;
|
|
if (approach == Approach::GTID)
|
|
{
|
|
gtid = get_gtid_current_pos(test, gc.nodes[0]);
|
|
cout << "GTID: " << gtid << endl;
|
|
}
|
|
|
|
MYSQL* pMaxscale = open_conn_no_db(maxscale_port, zMaxscale_host, "repl", "repl");
|
|
test.expect(pMaxscale, "Could not open connection to BLR at %s:%d.",
|
|
zMaxscale_host, maxscale_port);
|
|
|
|
if (pMaxscale)
|
|
{
|
|
if (setup_blr(test, pMaxscale, gtid, approach))
|
|
{
|
|
int slave_index = test.repl->N - 1; // We use the last slave.
|
|
|
|
Mariadb_nodes& ms = *test.repl;
|
|
ms.connect(slave_index);
|
|
|
|
MYSQL* pSlave = ms.nodes[slave_index];
|
|
mxb_assert(pSlave);
|
|
|
|
if (setup_slave(test, gtid, pSlave, zMaxscale_host, maxscale_port, approach))
|
|
{
|
|
if (setup_schema(test, gc.nodes[0]))
|
|
{
|
|
test_sleep(REPLICATION_SLEEP);
|
|
|
|
if (test.ok())
|
|
{
|
|
cout << endl;
|
|
test.tprintf("Testing basics.");
|
|
test_basics(test, pSlave);
|
|
}
|
|
|
|
if (test.ok())
|
|
{
|
|
cout << endl;
|
|
test.tprintf("Testing transparent switching of BLR master.");
|
|
|
|
if (setup_secondary_masters(test, pMaxscale))
|
|
{
|
|
test_multiple_masters(test, pSlave);
|
|
}
|
|
}
|
|
|
|
if (test.ok())
|
|
{
|
|
cout << endl;
|
|
test.tprintf("Testing functionality when master.ini is used.");
|
|
|
|
cout << "Stopping slave and MaxScale." << endl;
|
|
test.try_query(pSlave, "STOP SLAVE");
|
|
test.maxscales->stop();
|
|
|
|
cout << "Starting MaxScale." << endl;
|
|
test.maxscales->start();
|
|
test_sleep(5);
|
|
cout << "Starting slave." << endl;
|
|
test.try_query(pSlave, "START SLAVE");
|
|
test_sleep(3);
|
|
test_multiple_masters(test, pSlave);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mysql_close(pMaxscale);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Since setting the server ids can fail half-way, we run this irrespective
|
|
// of what setup_server_ids() returns.
|
|
restore_server_ids(test, server_ids_by_index);
|
|
|
|
if (!dont_setup_galera)
|
|
{
|
|
restore_galera(test);
|
|
}
|
|
|
|
return test.global_result;
|
|
}
|