do not explode if mysql transient exception class does not exist (#12213)

Follow up to #12205 to allow druid-mysql-extensions to work with mysql connector/j 8.x again, which does not contain MySQLTransientException, and while would have had the same problem as mariadb if a transient exception was checked, the new check eagerly loads the class when starting up, causing immediate failure.
This commit is contained in:
Clint Wylie 2022-01-31 19:36:24 -08:00 committed by GitHub
parent c4fa3ccfc4
commit 978b8f7dde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 33 deletions

View File

@ -19,7 +19,6 @@
package org.apache.druid.metadata.storage.mysql;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
@ -62,10 +61,10 @@ public class MySQLConnector extends SQLMetadataConnector
{
super(config, dbTables);
log.info("Loading \"MySQL\" metadata connector driver %s", driverConfig.getDriverClassName());
tryLoadDriverClass(driverConfig.getDriverClassName());
tryLoadDriverClass(driverConfig.getDriverClassName(), true);
if (driverConfig.getDriverClassName().contains("mysql")) {
myTransientExceptionClass = tryLoadDriverClass(MYSQL_TRANSIENT_EXCEPTION_CLASS_NAME);
myTransientExceptionClass = tryLoadDriverClass(MYSQL_TRANSIENT_EXCEPTION_CLASS_NAME, false);
} else {
myTransientExceptionClass = null;
}
@ -205,7 +204,11 @@ public class MySQLConnector extends SQLMetadataConnector
@Override
protected boolean connectorIsTransientException(Throwable e)
{
return isTransientException(myTransientExceptionClass, e);
if (myTransientExceptionClass != null) {
return myTransientExceptionClass.isAssignableFrom(e.getClass())
|| e instanceof SQLException && ((SQLException) e).getErrorCode() == 1317 /* ER_QUERY_INTERRUPTED */;
}
return false;
}
@Override
@ -241,28 +244,23 @@ public class MySQLConnector extends SQLMetadataConnector
return dbi;
}
private Class<?> tryLoadDriverClass(String className)
@Nullable
private Class<?> tryLoadDriverClass(String className, boolean failIfNotFound)
{
try {
return Class.forName(className, false, getClass().getClassLoader());
}
catch (ClassNotFoundException e) {
throw new ISE(e, "Could not find %s on the classpath. The MySQL Connector library is not included in the Druid "
+ "distribution but is required to use MySQL. Please download a compatible library (for example "
+ "'mysql-connector-java-5.1.48.jar') and place it under 'extensions/mysql-metadata-storage/'. See "
+ "https://druid.apache.org/downloads for more details.",
className
);
if (failIfNotFound) {
throw new ISE(e, "Could not find %s on the classpath. The MySQL Connector library is not included in the Druid "
+ "distribution but is required to use MySQL. Please download a compatible library (for example "
+ "'mysql-connector-java-5.1.48.jar') and place it under 'extensions/mysql-metadata-storage/'. See "
+ "https://druid.apache.org/downloads for more details.",
className
);
}
log.warn("Could not find %s on the classpath.", className);
return null;
}
}
@VisibleForTesting
static boolean isTransientException(@Nullable Class<?> driverClazz, Throwable e)
{
if (driverClazz != null) {
return driverClazz.isAssignableFrom(e.getClass())
|| e instanceof SQLException && ((SQLException) e).getErrorCode() == 1317 /* ER_QUERY_INTERRUPTED */;
}
return false;
}
}

View File

@ -19,8 +19,11 @@
package org.apache.druid.metadata.storage.mysql;
import com.google.common.base.Supplier;
import com.mysql.jdbc.exceptions.MySQLTransactionRollbackException;
import com.mysql.jdbc.exceptions.MySQLTransientException;
import org.apache.druid.metadata.MetadataStorageConnectorConfig;
import org.apache.druid.metadata.MetadataStorageTablesConfig;
import org.junit.Assert;
import org.junit.Test;
@ -29,37 +32,63 @@ import java.sql.SQLTransientConnectionException;
public class MySQLConnectorTest
{
@Test
public void testIsExceptionTransient()
private static final MySQLConnectorDriverConfig MYSQL_DRIVER_CONFIG = new MySQLConnectorDriverConfig();
private static final MySQLConnectorDriverConfig MARIADB_DRIVER_CONFIG = new MySQLConnectorDriverConfig()
{
final Class<?> clazz = MySQLTransientException.class;
Assert.assertTrue(MySQLConnector.isTransientException(clazz, new MySQLTransientException()));
Assert.assertTrue(MySQLConnector.isTransientException(clazz, new MySQLTransactionRollbackException()));
@Override
public String getDriverClassName()
{
return "org.mariadb.jdbc.Driver";
}
};
private static final Supplier<MetadataStorageConnectorConfig> CONNECTOR_CONFIG_SUPPLIER =
MetadataStorageConnectorConfig::new;
private static final Supplier<MetadataStorageTablesConfig> TABLES_CONFIG_SUPPLIER =
() -> new MetadataStorageTablesConfig(null, null, null, null, null, null, null, null, null, null, null);
@Test
public void testIsExceptionTransientMySql()
{
MySQLConnector connector = new MySQLConnector(
CONNECTOR_CONFIG_SUPPLIER,
TABLES_CONFIG_SUPPLIER,
new MySQLConnectorSslConfig(),
MYSQL_DRIVER_CONFIG
);
Assert.assertTrue(connector.connectorIsTransientException(new MySQLTransientException()));
Assert.assertTrue(connector.connectorIsTransientException(new MySQLTransactionRollbackException()));
Assert.assertTrue(
MySQLConnector.isTransientException(clazz, new SQLException("some transient failure", "wtf", 1317))
connector.connectorIsTransientException(new SQLException("some transient failure", "wtf", 1317))
);
Assert.assertFalse(
MySQLConnector.isTransientException(clazz, new SQLException("totally realistic test data", "wtf", 1337))
connector.connectorIsTransientException(new SQLException("totally realistic test data", "wtf", 1337))
);
// this method does not specially handle normal transient exceptions either, since it is not vendor specific
Assert.assertFalse(
MySQLConnector.isTransientException(clazz, new SQLTransientConnectionException("transient"))
connector.connectorIsTransientException(new SQLTransientConnectionException("transient"))
);
}
@Test
public void testIsExceptionTransientNoMySqlClazz()
{
MySQLConnector connector = new MySQLConnector(
CONNECTOR_CONFIG_SUPPLIER,
TABLES_CONFIG_SUPPLIER,
new MySQLConnectorSslConfig(),
MARIADB_DRIVER_CONFIG
);
// no vendor specific for MariaDb, so should always be false
Assert.assertFalse(MySQLConnector.isTransientException(null, new MySQLTransientException()));
Assert.assertFalse(connector.connectorIsTransientException(new MySQLTransientException()));
Assert.assertFalse(
MySQLConnector.isTransientException(null, new SQLException("some transient failure", "wtf", 1317))
connector.connectorIsTransientException(new SQLException("some transient failure", "wtf", 1317))
);
Assert.assertFalse(
MySQLConnector.isTransientException(null, new SQLException("totally realistic test data", "wtf", 1337))
connector.connectorIsTransientException(new SQLException("totally realistic test data", "wtf", 1337))
);
Assert.assertFalse(
MySQLConnector.isTransientException(null, new SQLTransientConnectionException("transient"))
connector.connectorIsTransientException(new SQLTransientConnectionException("transient"))
);
}
}