/* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/J is licensed under the terms of the GPLv2 , like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FOSS License Exception . This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package testsuite.fabric.jdbc; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.mysql.fabric.jdbc.FabricMySQLConnection; import com.mysql.fabric.jdbc.FabricMySQLDataSource; import testsuite.fabric.BaseFabricTestCase; /** * TODO: document required setup for this test */ public class TestHABasics extends BaseFabricTestCase { private FabricMySQLDataSource ds; private FabricMySQLConnection conn; private String masterPort = System.getProperty("com.mysql.fabric.testsuite.global.port"); public TestHABasics() throws Exception { super(); if (this.isSetForFabricTest) { this.ds = getNewDefaultDataSource(); } } @Override public void setUp() throws Exception { if (this.isSetForFabricTest) { this.conn = (FabricMySQLConnection) this.ds.getConnection(this.username, this.password); this.conn.setServerGroupName("ha_config1_group"); } } @Override public void tearDown() throws Exception { if (this.isSetForFabricTest) { this.conn.close(); } } private String getPort() throws Exception { ResultSet rs = this.conn.createStatement().executeQuery("show variables like 'port'"); assertTrue(rs.next()); String port1 = rs.getString(2); rs.close(); return port1; } /** * Test that writes go to the master and reads go to the slave(s). */ public void testReadWriteSplitting() throws Exception { if (!this.isSetForFabricTest) { return; } // make sure we start on the master assertEquals(this.masterPort, getPort()); Statement s = this.conn.createStatement(); s.executeUpdate("drop table if exists fruits"); s.executeUpdate("create table fruits (name varchar(30))"); s.executeUpdate("insert into fruits values ('Rambutan'), ('Starfruit')"); // go to the slave and verify this.conn.setReadOnly(true); assertTrue(!this.masterPort.equals(getPort())); // allow a little replication lag and check for data Thread.sleep(3000); ResultSet rs = s.executeQuery("select name from fruits order by 1"); assertTrue(rs.next()); assertEquals("Rambutan", rs.getString(1)); assertTrue(rs.next()); assertEquals("Starfruit", rs.getString(1)); assertFalse(rs.next()); rs.close(); this.conn.setReadOnly(false); s.executeUpdate("drop table fruits"); } /** * Test for failover that requires manual intervention. */ public void manualTestFailover() throws Exception { Statement s = this.conn.createStatement(); ResultSet rs = s.executeQuery("show variables like 'port'"); rs.next(); System.err.println("Starting master Port: " + rs.getString(2)); rs.close(); int secs = 15000; System.err.println("Sleeping " + (secs / 1000.0) + " seconds.... Please perform manual failover"); Thread.sleep(secs); System.err.println("Continuing"); try { rs = s.executeQuery("show variables like 'port'"); rs.close(); fail("Master should be unavailable"); } catch (SQLException ex) { rs = s.executeQuery("show variables like 'port'"); rs.next(); System.err.println("New master Port: " + rs.getString(2)); rs.close(); s.close(); } } /** * Test that new connections get the state changes from the Fabric node. The current implementation queries the Fabric node on every new connection and * therefore always has the latest state. Future shared state implementation may loosen this constraint. */ public void manualTestNewConnHasNewState() throws Exception { FabricMySQLConnection conn1 = (FabricMySQLConnection) this.ds.getConnection(this.username, this.password); conn1.setServerGroupName("ha_config1_group"); String query = "SELECT CONCAT(@@hostname, ':', @@port) AS 'Value'"; ResultSet rs = conn1.createStatement().executeQuery(query); rs.next(); String startingMaster = rs.getString(1); System.err.println("Starting master: " + startingMaster); rs.close(); conn1.close(); // allow time to perform manual failover int secs = 15000; System.err.println("Sleeping " + (secs / 1000.0) + " seconds.... Please perform manual failover"); Thread.sleep(secs); System.err.println("Continuing"); // do the exact same thing and expect it to connect to the new master without an exception conn1 = (FabricMySQLConnection) this.ds.getConnection(this.username, this.password); conn1.setServerGroupName("ha_config1_group"); rs = conn1.createStatement().executeQuery(query); rs.next(); String newMaster = rs.getString(1); System.err.println("New master: " + newMaster); assertFalse(startingMaster.equals(newMaster)); conn1.close(); } /** * Test that partially failed over connections (those that failed but could not immediately get a new connection) don't impact the creation of new * connections by being a part of the replication connection group. */ public void manualTestFailedOldMasterDoesntBlockNewConnections() throws Exception { FabricMySQLConnection conn1 = (FabricMySQLConnection) this.ds.getConnection(this.username, this.password); conn1.setServerGroupName("ha_config1_group"); Statement s = conn1.createStatement(); // run a query until a failure happens. this will cause the master connection in the replication connection to be closed try { while (true) { s.executeUpdate("set @x = 1"); try { Thread.sleep(500); } catch (Exception ex) { } } } catch (SQLException ex) { System.err.println("Failure encountered: " + ex.getMessage()); System.err.println("Waiting 10 seconds before trying a new connection"); try { Thread.sleep(10 * 1000); } catch (Exception ex2) { } } // we leave the conn *open* and therefore in the connection group to make sure it doesn't prevent changing master which would happen if the // removeMasterHost() call failed // make sure a new connection is successful conn1 = (FabricMySQLConnection) this.ds.getConnection(this.username, this.password); conn1.setServerGroupName("ha_config1_group"); ResultSet rs = conn1.createStatement().executeQuery("SELECT CONCAT(@@hostname, ':', @@port) AS 'Value'"); rs.next(); System.err.println("New master: " + rs.getString(1)); rs.close(); conn1.close(); } /** * Same as test `manualTestFailedOldMasterDoesntBlockNewConnections' for slaves. There must be only one slave in the HA group for this test. */ public void manualTestFailedSingleSlaveDoesntBlockNewConnections() throws Exception { FabricMySQLConnection conn1 = (FabricMySQLConnection) this.ds.getConnection(this.username, this.password); conn1.setServerGroupName("ha_config1_group"); conn1.setReadOnly(true); Statement s = conn1.createStatement(); // run a query until a failure happens. this will cause the slaves connection in the replication connection to be closed try { while (true) { ResultSet rs = s.executeQuery("select 1"); rs.close(); try { Thread.sleep(500); } catch (Exception ex) { } } } catch (SQLException ex) { System.err.println("Failure encountered: " + ex.getMessage()); System.err.println("Waiting 10 seconds before trying a new connection"); try { Thread.sleep(10 * 1000); } catch (Exception ex2) { } } // we leave the conn *open* and therefore in the connection group to make sure it doesn't prevent changing SLAVE which would happen if the // removeSlaveHost() call failed // make sure a new connection is successful conn1 = (FabricMySQLConnection) this.ds.getConnection(this.username, this.password); conn1.setServerGroupName("ha_config1_group"); conn1.setReadOnly(true); ResultSet rs = conn1.createStatement().executeQuery("SELECT CONCAT(@@hostname, ':', @@port) AS 'Value'"); rs.next(); System.err.println("New slave: " + rs.getString(1)); rs.close(); conn1.close(); } }