package org.mariadb.jdbc.failover;
import com.amazonaws.services.rds.model.DBInstanceNotFoundException;
import com.amazonaws.services.rds.model.InvalidDBClusterStateException;
import com.amazonaws.services.rds.model.InvalidDBInstanceStateException;
import com.amazonaws.services.rds.model.ModifyDBInstanceRequest;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mariadb.jdbc.HostAddress;
import org.mariadb.jdbc.internal.protocol.Protocol;
import org.mariadb.jdbc.internal.util.constant.HaMode;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
public class AuroraAutoDiscoveryTest extends BaseMultiHostTest {
/**
* Initialisation.
*
* @throws SQLException exception
*/
@BeforeClass()
public static void beforeClass2() throws SQLException {
proxyUrl = proxyAuroraUrl;
System.out.println("environment variable \"AURORA\" value : " + System.getenv("AURORA"));
Assume.assumeTrue(initialAuroraUrl != null && System.getenv("AURORA") != null && amazonRDSClient != null);
}
/**
* Initialisation.
*
* @throws SQLException exception
*/
@Before
public void init() throws SQLException {
defaultUrl = initialAuroraUrl;
currentType = HaMode.AURORA;
}
/**
* Creates a mock replica_host_status table to imitate the database used to retrieve information about the endpoints.
*
* @param insertEntryQuery - Query to insert a new entry into the table before running the tests
* @throws SQLException if unexpected error occur
*/
private Connection tableSetup(String insertEntryQuery) throws Throwable {
Connection connection = getNewConnection(true);
try (Statement statement = connection.createStatement()) {
statement.executeQuery("DROP TABLE IF EXISTS replica_host_status");
statement.executeQuery("CREATE TABLE replica_host_status (SERVER_ID VARCHAR(255), SESSION_ID VARCHAR(255), "
+ "LAST_UPDATE_TIMESTAMP TIMESTAMP DEFAULT NOW())");
ResultSet resultSet = statement.executeQuery("SELECT SERVER_ID, SESSION_ID, LAST_UPDATE_TIMESTAMP "
+ "FROM information_schema.replica_host_status "
+ "WHERE LAST_UPDATE_TIMESTAMP = ("
+ "SELECT MAX(LAST_UPDATE_TIMESTAMP) "
+ "FROM information_schema.replica_host_status)");
while (resultSet.next()) {
String values = "";
for (int i = 1; i < 4; i++) {
values += (i == 1) ? "'localhost'" : ",'" + resultSet.getString(i) + "'";
}
statement.executeQuery("INSERT INTO replica_host_status (SERVER_ID, SESSION_ID, LAST_UPDATE_TIMESTAMP) "
+ "VALUES (" + values + ")");
}
if (insertEntryQuery != null) {
statement.executeQuery(insertEntryQuery);
}
try {
setDbName(connection, "testj");
} catch (Throwable t) {
fail("Unable to set database for testing");
}
int serverId = getServerId(connection);
stopProxy(serverId, 1);
try (Statement statement2 = connection.createStatement()) {
statement2.executeQuery("select 1");
}
} catch (SQLException se) {
fail("Unable to execute queries to set up table: " + se);
}
return connection;
}
/**
* Takes down the table created solely for these tests.
*
* @throws SQLException if unexpected error occur
*/
@After
public void after() throws SQLException {
try (Connection connection = getNewConnection(true)) {
try (Statement statement = connection.createStatement()) {
statement.executeQuery("DROP TABLE IF EXISTS replica_host_status");
}
}
}
/**
* Test verifies that the driver discovers new instances as soon as they are available.
*
* @throws Throwable if unexpected error occur
*/
@Test
public void testDiscoverCreatedInstanceOnFailover() throws Throwable {
try (Connection connection = tableSetup(null)) {
int masterServerId = getServerId(connection);
final int initialSize = getProtocolFromConnection(connection).getUrlParser().getHostAddresses().size();
try (Statement statement = connection.createStatement()) {
statement.executeQuery("INSERT INTO replica_host_status (SERVER_ID, SESSION_ID) "
+ "VALUES ('test-discovery-on-creation', 'mock-new-endpoint')");
stopProxy(masterServerId, 1);
statement.executeQuery("select 1");
List<HostAddress> finalEndpoints = getProtocolFromConnection(connection).getUrlParser().getHostAddresses();
boolean newEndpointFound = foundHostInList(finalEndpoints, "test-discovery-on-creation");
assertTrue("Discovered new endpoint on failover", newEndpointFound);
assertEquals(initialSize + 1, finalEndpoints.size());
}
}
}
/**
* Test verifies that deleted instances are removed from the possible connections.
*
* @throws Throwable if unexpected error occur
*/
@Test
public void testRemoveDeletedInstanceOnFailover() throws Throwable {
try (Connection connection = tableSetup("INSERT INTO replica_host_status (SERVER_ID, SESSION_ID) "
+ "VALUES ('test-instance-deleted-detection', 'mock-delete-endpoint')")) {
Protocol protocol = getProtocolFromConnection(connection);
final int initialSize = protocol.getUrlParser().getHostAddresses().size();
int serverId = getServerId(connection);
try (Statement statement = connection.createStatement()) {
statement.executeQuery("UPDATE replica_host_status "
+ "SET LAST_UPDATE_TIMESTAMP = DATE_SUB(LAST_UPDATE_TIMESTAMP, INTERVAL 4 MINUTE) "
+ "WHERE SERVER_ID = 'test-instance-deleted-detection'");
stopProxy(serverId, 1);
statement.executeQuery("select 1");
}
List<HostAddress> finalEndpoints = protocol.getUrlParser().getHostAddresses();
boolean deletedInstanceGone = !foundHostInList(finalEndpoints, "test-instance-deleted-detection");
assertTrue("Removed deleted endpoint from urlParser", deletedInstanceGone);
assertEquals(initialSize - 1, finalEndpoints.size());
}
}
/**
* Must set newlyCreatedInstance system property in which the instance is not the current writer.
* The best way to test is to create a new instance as the test is started.
* All other instances should have a promotion tier greater than zero.
* Test checks if a newly created instance that is promoted as the writer is found and connected to right away.
*
* @throws Throwable if error occur
*/
@Test
public void testNewInstanceAsWriterDetection() throws Throwable {
Assume.assumeTrue("System property newlyCreatedInstance is set", System.getProperty("newlyCreatedInstance") != null);
try (Connection connection = getNewConnection(false)) {
final String initialHost = getProtocolFromConnection(connection).getHost();
ModifyDBInstanceRequest request1 = new ModifyDBInstanceRequest();
request1.setDBInstanceIdentifier(System.getProperty("newlyCreatedInstance"));
request1.setPromotionTier(0);
boolean promotionTierChanged;
do {
try {
amazonRDSClient.modifyDBInstance(request1);
promotionTierChanged = true;
} catch (InvalidDBInstanceStateException | DBInstanceNotFoundException e) {
promotionTierChanged = false;
}
} while (!promotionTierChanged);
try {
Thread.sleep(10 * 1000); // Should have completed modification
} catch (InterruptedException e) {
fail("Thread sleep was interrupted");
}
launchAuroraFailover();
try {
Thread.sleep(30 * 1000); // Should have failed over
} catch (InterruptedException e) {
fail("Thread sleep was interrupted");
}
try (Statement statement = connection.createStatement()) {
statement.executeQuery("select 1");
}
String newHost = getProtocolFromConnection(connection).getHost();
assertTrue("Connected to new writer", !initialHost.equals(newHost));
assertEquals(System.getProperty("newlyCreatedInstance"), newHost.substring(0, newHost.indexOf(".")));
}
}
@Test
public void testExceptionHandlingWhenDataFromTable() throws Throwable {
try (Connection connection = getNewConnection(false)) {
final String initialHost = getProtocolFromConnection(connection).getHost();
final Statement statement = connection.createStatement();
Thread queryThread = new Thread() {
public void run() {
long startTime = System.nanoTime();
long stopTime = System.nanoTime();
try {
while (Math.abs(TimeUnit.NANOSECONDS.toMillis(stopTime - startTime)) < 1000) {
stopTime = System.nanoTime();
statement.executeQuery("SELECT 1");
startTime = System.nanoTime();
}
} catch (SQLException se) {
se.printStackTrace();
}
}
};
Thread failoverThread = new Thread() {
public void run() {
do {
try {
launchAuroraFailover();
} catch (InvalidDBClusterStateException e) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
// Expected because may occur due to queryThread
}
}
} while (!isInterrupted());
}
};
queryThread.start();
failoverThread.start();
queryThread.join();
if (!queryThread.isAlive()) {
failoverThread.interrupt();
}
if (statement != null) {
statement.close();
}
Set<HostAddress> hostAddresses = getProtocolFromConnection(connection).getProxy().getListener().getBlacklistKeys();
boolean connectionBlacklisted = foundHostInList(hostAddresses, initialHost);
assertTrue("Connection has been blacklisted", connectionBlacklisted);
}
}
private boolean foundHostInList(Collection<HostAddress> hostAddresses, String hostIdentifier) {
for (HostAddress hostAddress : hostAddresses) {
if (hostAddress.host.indexOf(hostIdentifier) > -1) {
return true;
}
}
return false;
}
/**
* CONJ-392 : aurora must discover active nodes without timezone issue.
*
* @throws Throwable if error occur
*/
@Test
public void testTimeZoneDiscovery() throws Throwable {
try (Connection connection = getNewConnection("&sessionVariables=@@time_zone='US/Central'",false)) {
List<HostAddress> hostAddresses = getProtocolFromConnection(connection).getProxy().getListener().getUrlParser().getHostAddresses();
for (HostAddress hostAddress : hostAddresses) {
System.out.println("hostAddress:" + hostAddress);
}
assertTrue(hostAddresses.size() > 1);
}
}
}