Search in sources :

Example 1 with ReplicationConnectionUrl

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();
}
Also used : HashMap(java.util.HashMap) ConnectionUrl(com.mysql.cj.conf.ConnectionUrl) ReplicationConnectionUrl(com.mysql.cj.conf.url.ReplicationConnectionUrl) Properties(java.util.Properties) HostInfo(com.mysql.cj.conf.HostInfo) SQLFeatureNotSupportedException(java.sql.SQLFeatureNotSupportedException) SQLTransientException(java.sql.SQLTransientException) InvocationTargetException(java.lang.reflect.InvocationTargetException) XAException(javax.transaction.xa.XAException) SocketException(java.net.SocketException) SQLClientInfoException(java.sql.SQLClientInfoException) SQLException(java.sql.SQLException) SocketTimeoutException(java.net.SocketTimeoutException) IOException(java.io.IOException) PasswordExpiredException(com.mysql.cj.exceptions.PasswordExpiredException) ExecutionException(java.util.concurrent.ExecutionException) TimeoutException(java.util.concurrent.TimeoutException) SQLNonTransientConnectionException(java.sql.SQLNonTransientConnectionException) CommunicationsException(com.mysql.cj.jdbc.exceptions.CommunicationsException) CertificateException(java.security.cert.CertificateException) ClosedOnExpiredPasswordException(com.mysql.cj.exceptions.ClosedOnExpiredPasswordException) PropertyNotModifiableException(com.mysql.cj.exceptions.PropertyNotModifiableException) ReplicationConnectionUrl(com.mysql.cj.conf.url.ReplicationConnectionUrl) ReplicationConnection(com.mysql.cj.jdbc.ha.ReplicationConnection) Test(org.junit.jupiter.api.Test)

Example 2 with ReplicationConnectionUrl

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;
}
Also used : HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) HostInfo(com.mysql.cj.conf.HostInfo) ReplicationConnection(com.mysql.cj.jdbc.ha.ReplicationConnection) ReplicationConnectionUrl(com.mysql.cj.conf.url.ReplicationConnectionUrl)

Aggregations

HostInfo (com.mysql.cj.conf.HostInfo)2 ReplicationConnectionUrl (com.mysql.cj.conf.url.ReplicationConnectionUrl)2 ReplicationConnection (com.mysql.cj.jdbc.ha.ReplicationConnection)2 HashMap (java.util.HashMap)2 ConnectionUrl (com.mysql.cj.conf.ConnectionUrl)1 ClosedOnExpiredPasswordException (com.mysql.cj.exceptions.ClosedOnExpiredPasswordException)1 PasswordExpiredException (com.mysql.cj.exceptions.PasswordExpiredException)1 PropertyNotModifiableException (com.mysql.cj.exceptions.PropertyNotModifiableException)1 CommunicationsException (com.mysql.cj.jdbc.exceptions.CommunicationsException)1 IOException (java.io.IOException)1 InvocationTargetException (java.lang.reflect.InvocationTargetException)1 SocketException (java.net.SocketException)1 SocketTimeoutException (java.net.SocketTimeoutException)1 CertificateException (java.security.cert.CertificateException)1 SQLClientInfoException (java.sql.SQLClientInfoException)1 SQLException (java.sql.SQLException)1 SQLFeatureNotSupportedException (java.sql.SQLFeatureNotSupportedException)1 SQLNonTransientConnectionException (java.sql.SQLNonTransientConnectionException)1 SQLTransientException (java.sql.SQLTransientException)1 ArrayList (java.util.ArrayList)1