use of com.mysql.cj.conf.url.ReplicationConnectionUrl in project aws-mysql-jdbc by awslabs.
the class ConnectionRegressionTest method testBug22678872.
/**
* Tests fix for Bug#22678872 - NPE DURING UPDATE WITH FABRIC.
*
* Although the bug was reported against a Fabric connection, it can't be systematically reproduced there. A deep analysis revealed that the bug occurs due
* to a defect in the dynamic hosts management of replication connections, specifically when one or both of the internal hosts lists (sources and/or
* replicas)
* becomes empty. As such, the bug is reproducible and tested resorting to replication connections and dynamic hosts management of replication connections
* only.
* This test reproduces the relevant steps involved in the original stack trace, originated in the FabricMySQLConnectionProxy.getActiveConnection() code:
* - The replication connections are initialized with the same properties as in a Fabric connection.
* - Hosts are removed using the same options as in a Fabric connection.
* - The method tested after any host change is Connection.setAutoCommit(), which is the method that triggered the original NPE.
*
* @throws Exception
*/
@Test
public void testBug22678872() throws Exception {
final Properties connProps = getPropertiesFromTestsuiteUrl();
final String host = connProps.getProperty(PropertyKey.HOST.getKeyName(), "localhost");
final String port = connProps.getProperty(PropertyKey.PORT.getKeyName(), "3306");
final String hostPortPair = host + ":" + port;
final String database = connProps.getProperty(PropertyKey.DBNAME.getKeyName());
final String username = connProps.getProperty(PropertyKey.USER.getKeyName());
final String password = connProps.getProperty(PropertyKey.PASSWORD.getKeyName(), "");
final String connectionTimeZone = connProps.getProperty(PropertyKey.connectionTimeZone.getKeyName());
final Map<String, String> props = new HashMap<>();
props.put(PropertyKey.USER.getKeyName(), username);
props.put(PropertyKey.PASSWORD.getKeyName(), password);
props.put(PropertyKey.DBNAME.getKeyName(), database);
props.put(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
props.put(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
// Speed up the test execution.
props.put(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "0");
// Replicate the properties used in FabricMySQLConnectionProxy.getActiveConnection().
props.put(PropertyKey.retriesAllDown.getKeyName(), "1");
props.put(PropertyKey.allowSourceDownConnections.getKeyName(), "true");
props.put(PropertyKey.allowReplicaDownConnections.getKeyName(), "true");
props.put(PropertyKey.readFromSourceWhenNoReplicas.getKeyName(), "true");
if (connectionTimeZone != null) {
props.put(PropertyKey.connectionTimeZone.getKeyName(), connectionTimeZone);
}
ConnectionUrl replConnectionUrl = new ReplicationConnectionUrl(Collections.<HostInfo>emptyList(), Collections.<HostInfo>emptyList(), props);
String replConnGroup = "";
final List<HostInfo> emptyHostsList = Collections.emptyList();
final List<HostInfo> singleHostList = Collections.singletonList(replConnectionUrl.getHostOrSpawnIsolated(hostPortPair));
/*
* Case A:
* - Initialize a replication connection with sources and replicas lists empty.
*/
replConnGroup = "Bug22678872A";
props.put(PropertyKey.replicationConnectionGroup.getKeyName(), replConnGroup);
assertThrows(SQLException.class, "A replication connection cannot be initialized without source hosts and replica hosts, simultaneously\\.", new Callable<Void>() {
public Void call() throws Exception {
ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(emptyHostsList, emptyHostsList, props));
return null;
}
});
/*
* Case B:
* - Initialize a replication connection with one source and no replicas.
* - Then remove the source and add it back as a replica, followed by a promotion to source.
*/
replConnGroup = "Bug22678872B";
props.put(PropertyKey.replicationConnectionGroup.getKeyName(), replConnGroup);
final ReplicationConnection testConnB = ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(singleHostList, emptyHostsList, props));
// Connected to a source host.
assertTrue(testConnB.isSourceConnection());
assertFalse(testConnB.isReadOnly());
// This was the method that triggered the original NPE.
testConnB.setAutoCommit(false);
ReplicationConnectionGroupManager.removeSourceHost(replConnGroup, hostPortPair, false);
assertThrows(SQLException.class, "The replication connection is an inconsistent state due to non existing hosts in both its internal hosts lists\\.", new Callable<Void>() {
public Void call() throws Exception {
// JDBC interface method throws SQLException.
testConnB.setAutoCommit(false);
return null;
}
});
assertThrows(IllegalStateException.class, "The replication connection is an inconsistent state due to non existing hosts in both its internal hosts lists\\.", new Callable<Void>() {
public Void call() throws Exception {
// Some Connector/J internal methods don't throw compatible exceptions. They have to be wrapped.
testConnB.isSourceConnection();
return null;
}
});
ReplicationConnectionGroupManager.addReplicaHost(replConnGroup, hostPortPair);
// Connected to a replica host.
assertFalse(testConnB.isSourceConnection());
assertTrue(testConnB.isReadOnly());
testConnB.setAutoCommit(false);
ReplicationConnectionGroupManager.promoteReplicaToSource(replConnGroup, hostPortPair);
// Connected to a source host.
assertTrue(testConnB.isSourceConnection());
assertFalse(testConnB.isReadOnly());
testConnB.setAutoCommit(false);
testConnB.close();
/*
* Case C:
* - Initialize a replication connection with no sources and one replica.
* - Then remove the replica and add it back, followed by a promotion to source.
*/
replConnGroup = "Bug22678872C";
props.put(PropertyKey.replicationConnectionGroup.getKeyName(), replConnGroup);
final ReplicationConnection testConnC = ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(emptyHostsList, singleHostList, props));
// Connected to a replica host.
assertFalse(testConnC.isSourceConnection());
assertTrue(testConnC.isReadOnly());
testConnC.setAutoCommit(false);
ReplicationConnectionGroupManager.removeReplicaHost(replConnGroup, hostPortPair, true);
assertThrows(SQLException.class, "The replication connection is an inconsistent state due to non existing hosts in both its internal hosts lists\\.", new Callable<Void>() {
public Void call() throws Exception {
testConnC.setAutoCommit(false);
return null;
}
});
ReplicationConnectionGroupManager.addReplicaHost(replConnGroup, hostPortPair);
// Connected to a replica host.
assertFalse(testConnC.isSourceConnection());
assertTrue(testConnC.isReadOnly());
testConnC.setAutoCommit(false);
ReplicationConnectionGroupManager.promoteReplicaToSource(replConnGroup, hostPortPair);
// Connected to a source host ...
assertTrue(testConnC.isSourceConnection());
// ... but the connection is read-only because it was initialized with no sources.
assertTrue(testConnC.isReadOnly());
testConnC.setAutoCommit(false);
testConnC.close();
/*
* Case D:
* - Initialize a replication connection with one source and one replica.
* - Then remove the source host, followed by removing the replica host.
* - Finally add the replica host back and promote it to source.
*/
replConnGroup = "Bug22678872D";
props.put(PropertyKey.replicationConnectionGroup.getKeyName(), replConnGroup);
final ReplicationConnection testConnD = ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(singleHostList, singleHostList, props));
// Connected to a source host.
assertTrue(testConnD.isSourceConnection());
assertFalse(testConnD.isReadOnly());
testConnD.setAutoCommit(false);
ReplicationConnectionGroupManager.removeSourceHost(replConnGroup, hostPortPair, false);
// Connected to a replica host.
assertFalse(testConnD.isSourceConnection());
assertTrue(testConnD.isReadOnly());
testConnD.setAutoCommit(false);
ReplicationConnectionGroupManager.removeReplicaHost(replConnGroup, hostPortPair, true);
assertThrows(SQLException.class, "The replication connection is an inconsistent state due to non existing hosts in both its internal hosts lists\\.", new Callable<Void>() {
public Void call() throws Exception {
testConnD.setAutoCommit(false);
return null;
}
});
ReplicationConnectionGroupManager.addReplicaHost(replConnGroup, hostPortPair);
// Connected to a replica host.
assertFalse(testConnD.isSourceConnection());
assertTrue(testConnD.isReadOnly());
testConnD.setAutoCommit(false);
ReplicationConnectionGroupManager.promoteReplicaToSource(replConnGroup, hostPortPair);
// Connected to a source host.
assertTrue(testConnD.isSourceConnection());
assertFalse(testConnD.isReadOnly());
testConnD.setAutoCommit(false);
testConnD.close();
/*
* Case E:
* - Initialize a replication connection with one source and one replica.
* - Set read-only.
* - Then remove the replica host, followed by removing the source host.
* - Finally add the replica host back and promote it to source.
*/
replConnGroup = "Bug22678872E";
props.put(PropertyKey.replicationConnectionGroup.getKeyName(), replConnGroup);
final ReplicationConnection testConnE = ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(singleHostList, singleHostList, props));
// Connected to a source host.
assertTrue(testConnE.isSourceConnection());
assertFalse(testConnE.isReadOnly());
testConnE.setAutoCommit(false);
testConnE.setReadOnly(true);
// Connected to a replica host.
assertFalse(testConnE.isSourceConnection());
assertTrue(testConnE.isReadOnly());
testConnE.setAutoCommit(false);
ReplicationConnectionGroupManager.removeReplicaHost(replConnGroup, hostPortPair, true);
// Connected to a source host...
assertTrue(testConnE.isSourceConnection());
// ... but the connection is read-only because that's how it was previously set.
assertTrue(testConnE.isReadOnly());
testConnE.setAutoCommit(false);
ReplicationConnectionGroupManager.removeSourceHost(replConnGroup, hostPortPair, false);
assertThrows(SQLException.class, "The replication connection is an inconsistent state due to non existing hosts in both its internal hosts lists\\.", new Callable<Void>() {
public Void call() throws Exception {
testConnE.setAutoCommit(false);
return null;
}
});
ReplicationConnectionGroupManager.addReplicaHost(replConnGroup, hostPortPair);
// Connected to a replica host.
assertFalse(testConnE.isSourceConnection());
assertTrue(testConnE.isReadOnly());
testConnE.setAutoCommit(false);
ReplicationConnectionGroupManager.promoteReplicaToSource(replConnGroup, hostPortPair);
// Connected to a source host...
assertTrue(testConnE.isSourceConnection());
// ... but the connection is read-only because that's how it was previously set.
assertTrue(testConnE.isReadOnly());
testConnE.setAutoCommit(false);
testConnE.close();
/*
* Case F:
* - Initialize a replication connection with one source and one replica.
* - Then remove the replica host, followed by removing the source host.
* - Finally add the replica host back and promote it to source.
*/
replConnGroup = "Bug22678872F";
props.put(PropertyKey.replicationConnectionGroup.getKeyName(), replConnGroup);
final ReplicationConnection testConnF = ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(singleHostList, singleHostList, props));
// Connected to a source host.
assertTrue(testConnF.isSourceConnection());
assertFalse(testConnF.isReadOnly());
testConnF.setAutoCommit(false);
ReplicationConnectionGroupManager.removeReplicaHost(replConnGroup, hostPortPair, true);
// Connected to a source host.
assertTrue(testConnF.isSourceConnection());
assertFalse(testConnF.isReadOnly());
testConnF.setAutoCommit(false);
ReplicationConnectionGroupManager.removeSourceHost(replConnGroup, hostPortPair, false);
assertThrows(SQLException.class, "The replication connection is an inconsistent state due to non existing hosts in both its internal hosts lists\\.", new Callable<Void>() {
public Void call() throws Exception {
testConnF.setAutoCommit(false);
return null;
}
});
ReplicationConnectionGroupManager.addReplicaHost(replConnGroup, hostPortPair);
// Connected to a replica host.
assertFalse(testConnF.isSourceConnection());
assertTrue(testConnF.isReadOnly());
testConnF.setAutoCommit(false);
ReplicationConnectionGroupManager.promoteReplicaToSource(replConnGroup, hostPortPair);
// Connected to a source host.
assertTrue(testConnF.isSourceConnection());
assertFalse(testConnF.isReadOnly());
testConnF.setAutoCommit(false);
testConnF.close();
/*
* Case G:
* This covers one corner case where the attribute ReplicationConnectionProxy.currentConnection can still be null even when there are known hosts. It
* results from a combination of empty hosts lists with downed hosts:
* - Start with one host in each list.
* - Switch to the replicas connection (set read-only).
* - Remove the source host.
* - Make the replica only unavailable.
* - Promote the replica host to source.
* - (At this point the active connection is "null")
* - Finally bring up the host again and check the connection status.
*/
// Use the UnreliableSocketFactory to control when the host must be downed.
props.remove(PropertyKey.replicationConnectionGroup.getKeyName());
props.put(PropertyKey.socketFactory.getKeyName(), "testsuite.UnreliableSocketFactory");
replConnectionUrl = new ReplicationConnectionUrl(Collections.<HostInfo>emptyList(), Collections.<HostInfo>emptyList(), props);
final String newHost = "bug22678872";
final String newHostPortPair = newHost + ":" + port;
final String hostConnected = UnreliableSocketFactory.getHostConnectedStatus(newHost);
final String hostNotConnected = UnreliableSocketFactory.getHostFailedStatus(newHost);
final List<HostInfo> newSingleHostList = Collections.singletonList(replConnectionUrl.getHostOrSpawnIsolated(newHostPortPair));
UnreliableSocketFactory.flushAllStaticData();
UnreliableSocketFactory.mapHost(newHost, host);
replConnGroup = "Bug22678872G";
props.put(PropertyKey.replicationConnectionGroup.getKeyName(), replConnGroup);
final ReplicationConnection testConnG = ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(newSingleHostList, newSingleHostList, props));
// Connected to a source host.
assertTrue(testConnG.isSourceConnection());
assertFalse(testConnG.isReadOnly());
testConnG.setAutoCommit(false);
// Two successful connections.
testBug22678872CheckConnectionsHistory(hostConnected, hostConnected);
testConnG.setReadOnly(true);
// Connected to a replica host.
assertFalse(testConnG.isSourceConnection());
assertTrue(testConnG.isReadOnly());
testConnG.setAutoCommit(false);
ReplicationConnectionGroupManager.removeSourceHost(replConnGroup, newHostPortPair, false);
// Connected to a replica host.
assertFalse(testConnG.isSourceConnection());
assertTrue(testConnG.isReadOnly());
testConnG.setAutoCommit(false);
// The host (currently a replica) goes down before being promoted to source.
UnreliableSocketFactory.downHost(newHost);
assertThrows(SQLException.class, "(?s)Communications link failure.*", new Callable<Void>() {
public Void call() throws Exception {
testConnG.promoteReplicaToSource(newHostPortPair);
return null;
}
});
// One failed connection attempt.
testBug22678872CheckConnectionsHistory(hostNotConnected);
// Actually not connected, but the promotion to source succeeded.
assertFalse(testConnG.isSourceConnection());
assertThrows(SQLException.class, "The connection is unusable at the current state\\. There may be no hosts to connect to or all hosts this " + "connection knows may be down at the moment\\.", new Callable<Void>() {
public Void call() throws Exception {
testConnG.setAutoCommit(false);
return null;
}
});
// Another failed connection attempt.
testBug22678872CheckConnectionsHistory(hostNotConnected);
assertThrows(SQLException.class, "(?s)Communications link failure.*", new Callable<Void>() {
public Void call() throws Exception {
// Triggers a reconnection that fails. The read-only state change is canceled by the exception.
testConnG.setReadOnly(false);
return null;
}
});
// This throws a comm failure because it tried to connect to the existing server and failed. The internal read-only state didn't change.
// Another failed connection attempt.
testBug22678872CheckConnectionsHistory(hostNotConnected);
// The host (currently a source) is up again.
UnreliableSocketFactory.dontDownHost(newHost);
// Triggers a reconnection that succeeds.
testConnG.setAutoCommit(false);
// One successful connection.
testBug22678872CheckConnectionsHistory(hostConnected);
// Connected to a source host...
assertTrue(testConnG.isSourceConnection());
// ... but the connection is read-only because that's how it was previously set.
assertTrue(testConnG.isReadOnly());
testConnG.setAutoCommit(false);
testConnG.close();
}
use of com.mysql.cj.conf.url.ReplicationConnectionUrl in project aws-mysql-jdbc by awslabs.
the class ConnectionRegressionTest method getTestReplicationConnectionNoReplicas.
/**
* Internal method for tests to get a replication connection with a
* single source host to the test URL.
*
* @param sourceHost
* @param props
* @return a replication connection
* @throws Exception
*/
private ReplicationConnection getTestReplicationConnectionNoReplicas(String sourceHost, Properties props) throws Exception {
List<HostInfo> sourceHosts = new ArrayList<>();
sourceHosts.add(mainConnectionUrl.getHostOrSpawnIsolated(sourceHost));
// empty
List<HostInfo> replicaHosts = new ArrayList<>();
Map<String, String> properties = new HashMap<>();
props.stringPropertyNames().stream().forEach(k -> properties.put(k, props.getProperty(k)));
ReplicationConnection replConn = ReplicationConnectionProxy.createProxyInstance(new ReplicationConnectionUrl(sourceHosts, replicaHosts, properties));
return replConn;
}
Aggregations