package org.mariadb.jdbc.failover; import org.junit.Assume; import org.junit.Test; import org.mariadb.jdbc.HostAddress; import org.mariadb.jdbc.MariaDbPreparedStatementServer; import java.sql.*; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.*; public abstract class BaseReplication extends BaseMonoServer { @Test public void failoverSlaveToMasterPrepareStatement() throws Throwable { try (Connection connection = getNewConnection( "&retriesAllDown=6&connectTimeout=1000&socketTimeout=1000&useBatchMultiSend=false", true)) { Statement stmt = connection.createStatement(); stmt.execute("drop table if exists replicationFailoverBinary" + jobId); stmt.execute("create table replicationFailoverBinary" + jobId + " (id int not null primary key auto_increment, test VARCHAR(10))"); stmt.execute("insert into replicationFailoverBinary" + jobId + "(test) values ('Harriba !')"); int masterServerId = getServerId(connection); connection.setReadOnly(true); //wait for table replication on slave Thread.sleep(200); //create another prepareStatement, to permit to verify that prepare id has changed connection.prepareStatement("SELECT ?"); //prepareStatement on slave connection PreparedStatement preparedStatement = connection.prepareStatement("SELECT test from replicationFailoverBinary" + jobId + " where id = ?"); final long currentPrepareId = getPrepareResult((MariaDbPreparedStatementServer) preparedStatement).getStatementId(); int slaveServerId = getServerId(connection); assertFalse(masterServerId == slaveServerId); //stop slave for a few seconds stopProxy(slaveServerId, 2000); //test failover preparedStatement.setInt(1, 1); ResultSet rs = preparedStatement.executeQuery(); rs.next(); assertEquals("Harriba !", rs.getString(1)); assertNotEquals(currentPrepareId, getPrepareResult((MariaDbPreparedStatementServer) preparedStatement).getStatementId()); int currentServerId = getServerId(connection); assertTrue(masterServerId == currentServerId); assertFalse(connection.isReadOnly()); Thread.sleep(2000); boolean hasReturnOnSlave = false; for (int i = 0; i < 10; i++) { Thread.sleep(1000); preparedStatement.setInt(1, 1); rs = preparedStatement.executeQuery(); rs.next(); assertEquals("Harriba !", rs.getString(1)); currentServerId = getServerId(connection); if (currentServerId != masterServerId) { hasReturnOnSlave = true; assertTrue(connection.isReadOnly()); break; } } assertTrue("Prepare statement has not return on Slave",hasReturnOnSlave); } } @Test() public void failoverSlaveAndMasterRewrite() throws Throwable { try (Connection connection = getNewConnection( "&rewriteBatchedStatements=true&retriesAllDown=6&connectTimeout=2000&socketTimeout=2000", true)) { int masterServerId = getServerId(connection); connection.setReadOnly(true); int firstSlaveId = getServerId(connection); stopProxy(masterServerId); //stop proxy for 2s stopProxy(firstSlaveId, 4000); try { Statement stmt = connection.createStatement(); stmt.addBatch("DO 1"); stmt.addBatch("DO 2"); int[] resultData = stmt.executeBatch(); int secondSlaveId = getServerId(connection); assertEquals("the 2 batch queries must have been executed when failover", 2, resultData.length); assertTrue(secondSlaveId != firstSlaveId && secondSlaveId != masterServerId); } catch (SQLException e) { e.printStackTrace(); fail(); } } } @Test public void failoverSlaveToMaster() throws Throwable { try (Connection connection = getNewConnection("&retriesAllDown=6&connectTimeout=1000&socketTimeout=1000", true)) { int masterServerId = getServerId(connection); connection.setReadOnly(true); int slaveServerId = getServerId(connection); assertFalse(masterServerId == slaveServerId); stopProxy(slaveServerId); connection.createStatement().execute("SELECT 1"); int currentServerId = getServerId(connection); assertTrue(masterServerId == currentServerId); assertFalse(connection.isReadOnly()); } } @Test public void failoverDuringSlaveSetReadOnly() throws Throwable { try (Connection connection = getNewConnection("&socketTimeout=3000", true)) { connection.setReadOnly(true); int slaveServerId = getServerId(connection); stopProxy(slaveServerId, 2000); connection.setReadOnly(false); int masterServerId = getServerId(connection); assertFalse(slaveServerId == masterServerId); assertFalse(connection.isReadOnly()); } Thread.sleep(2500); //for not interfering with other tests } @Test() public void failoverSlaveAndMasterWithoutAutoConnect() throws Throwable { try (Connection connection = getNewConnection("&retriesAllDown=20&connectTimeout=2000&socketTimeout=2000", true)) { int masterServerId = getServerId(connection); connection.setReadOnly(true); int firstSlaveId = getServerId(connection); stopProxy(masterServerId); stopProxy(firstSlaveId); try { //will connect to second slave that isn't stopped connection.createStatement().executeQuery("SELECT CONNECTION_ID()"); } catch (SQLException e) { e.printStackTrace(); fail(); } } } @Test public void reconnectSlaveAndMasterWithAutoConnect() throws Throwable { try (Connection connection = getNewConnection( "&retriesAllDown=6&connectTimeout=2000&socketTimeout=2000", true)) { //search actual server_id for master and slave int masterServerId = getServerId(connection); connection.setReadOnly(true); int firstSlaveId = getServerId(connection); stopProxy(masterServerId); stopProxy(firstSlaveId); //must reconnect to the second slave without error connection.createStatement().execute("SELECT 1"); int currentSlaveId = getServerId(connection); assertTrue(currentSlaveId != firstSlaveId); assertTrue(currentSlaveId != masterServerId); } } @Test public void failoverMasterWithAutoConnect() throws Throwable { try (Connection connection = getNewConnection( "&retriesAllDown=6&connectTimeout=1000&socketTimeout=1000", true)) { int masterServerId = getServerId(connection); stopProxy(masterServerId, 250); //with autoreconnect, the connection must reconnect automatically int currentServerId = getServerId(connection); assertTrue(currentServerId == masterServerId); assertFalse(connection.isReadOnly()); } Thread.sleep(500); //for not interfering with other tests } @Test public void writeToSlaveAfterFailover() throws Throwable { try (Connection connection = getNewConnection("&retriesAllDown=6&connectTimeout=1000&socketTimeout=1000", true)) { //if super user can write on slave Assume.assumeTrue(!hasSuperPrivilege(connection, "writeToSlaveAfterFailover")); Statement st = connection.createStatement(); st.execute("drop table if exists writeToSlave" + jobId); st.execute("create table writeToSlave" + jobId + " (id int not null primary key , amount int not null) ENGINE = InnoDB"); st.execute("insert into writeToSlave" + jobId + " (id, amount) VALUE (1 , 100)"); int masterServerId = getServerId(connection); stopProxy(masterServerId); try { st.execute("insert into writeToSlave" + jobId + " (id, amount) VALUE (2 , 100)"); fail(); } catch (SQLException e) { //normal exception restartProxy(masterServerId); st = connection.createStatement(); st.execute("drop table if exists writeToSlave" + jobId); } } } @Test public void randomConnection() throws Throwable { Map<HostAddress, MutableInt> connectionMap = new HashMap<>(); int masterId = -1; for (int i = 0; i < 20; i++) { try (Connection connection = getNewConnection(false)) { ; int serverId = getServerId(connection); if (i > 0) { assertTrue(masterId == serverId); } masterId = serverId; connection.setReadOnly(true); HostAddress replicaHost = getServerHostAddress(connection); MutableInt count = connectionMap.get(replicaHost); if (count == null) { connectionMap.put(replicaHost, new MutableInt()); } else { count.increment(); } } } assertTrue(connectionMap.size() >= 2); for (HostAddress key : connectionMap.keySet()) { Integer connectionCount = connectionMap.get(key).get(); assertTrue(connectionCount > 1); } } @Test public void closeWhenInReconnectionLoop() throws Throwable { try (Connection connection = getNewConnection("&connectTimeout=1000&socketTimeout=1000", true)) { int masterId = getServerId(connection); connection.setReadOnly(true); //close all slave proxy stopProxyButParameter(masterId); //trigger the failover, so a failover thread is launched Statement stmt = connection.createStatement(); stmt.execute("SELECT 1"); //launch connection close during failover must not throw error Thread.sleep(200); } } @Test public void failoverSlaveToMasterFail() throws Throwable { try (Connection connection = getNewConnection("&connectTimeout=1000&socketTimeout=1000&retriesAllDown=6", true)) { int masterServerId = getServerId(connection); connection.setReadOnly(true); int slaveServerId = getServerId(connection); assertTrue(slaveServerId != masterServerId); connection.setCatalog("mysql"); //to be sure there will be a query, and so an error when switching connection stopProxy(masterServerId); try { //must throw error connection.setReadOnly(false); fail(); } catch (SQLException e) { //normal exception } restartProxy(masterServerId); } } @Test public void failoverDuringMasterSetReadOnly() throws Throwable { try (Connection connection = getNewConnection("&retriesAllDown=6", true)) { int masterServerId = -1; masterServerId = getServerId(connection); stopProxy(masterServerId); connection.setReadOnly(true); int slaveServerId = getServerId(connection); assertFalse(slaveServerId == masterServerId); assertTrue(connection.isReadOnly()); restartProxy(masterServerId); } } class MutableInt { private int value = 1; // note that we start at 1 since we're counting public void increment() { ++value; } public int get() { return value; } } }