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; package org.apache.druid.metadata.storage.mysql;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -62,10 +61,10 @@ public class MySQLConnector extends SQLMetadataConnector
{ {
super(config, dbTables); super(config, dbTables);
log.info("Loading \"MySQL\" metadata connector driver %s", driverConfig.getDriverClassName()); log.info("Loading \"MySQL\" metadata connector driver %s", driverConfig.getDriverClassName());
tryLoadDriverClass(driverConfig.getDriverClassName()); tryLoadDriverClass(driverConfig.getDriverClassName(), true);
if (driverConfig.getDriverClassName().contains("mysql")) { if (driverConfig.getDriverClassName().contains("mysql")) {
myTransientExceptionClass = tryLoadDriverClass(MYSQL_TRANSIENT_EXCEPTION_CLASS_NAME); myTransientExceptionClass = tryLoadDriverClass(MYSQL_TRANSIENT_EXCEPTION_CLASS_NAME, false);
} else { } else {
myTransientExceptionClass = null; myTransientExceptionClass = null;
} }
@ -205,7 +204,11 @@ public class MySQLConnector extends SQLMetadataConnector
@Override @Override
protected boolean connectorIsTransientException(Throwable e) 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 @Override
@ -241,28 +244,23 @@ public class MySQLConnector extends SQLMetadataConnector
return dbi; return dbi;
} }
private Class<?> tryLoadDriverClass(String className) @Nullable
private Class<?> tryLoadDriverClass(String className, boolean failIfNotFound)
{ {
try { try {
return Class.forName(className, false, getClass().getClassLoader()); return Class.forName(className, false, getClass().getClassLoader());
} }
catch (ClassNotFoundException e) { catch (ClassNotFoundException e) {
throw new ISE(e, "Could not find %s on the classpath. The MySQL Connector library is not included in the Druid " if (failIfNotFound) {
+ "distribution but is required to use MySQL. Please download a compatible library (for example " throw new ISE(e, "Could not find %s on the classpath. The MySQL Connector library is not included in the Druid "
+ "'mysql-connector-java-5.1.48.jar') and place it under 'extensions/mysql-metadata-storage/'. See " + "distribution but is required to use MySQL. Please download a compatible library (for example "
+ "https://druid.apache.org/downloads for more details.", + "'mysql-connector-java-5.1.48.jar') and place it under 'extensions/mysql-metadata-storage/'. See "
className + "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; package org.apache.druid.metadata.storage.mysql;
import com.google.common.base.Supplier;
import com.mysql.jdbc.exceptions.MySQLTransactionRollbackException; import com.mysql.jdbc.exceptions.MySQLTransactionRollbackException;
import com.mysql.jdbc.exceptions.MySQLTransientException; 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.Assert;
import org.junit.Test; import org.junit.Test;
@ -29,37 +32,63 @@ import java.sql.SQLTransientConnectionException;
public class MySQLConnectorTest public class MySQLConnectorTest
{ {
@Test private static final MySQLConnectorDriverConfig MYSQL_DRIVER_CONFIG = new MySQLConnectorDriverConfig();
public void testIsExceptionTransient() private static final MySQLConnectorDriverConfig MARIADB_DRIVER_CONFIG = new MySQLConnectorDriverConfig()
{ {
final Class<?> clazz = MySQLTransientException.class; @Override
Assert.assertTrue(MySQLConnector.isTransientException(clazz, new MySQLTransientException())); public String getDriverClassName()
Assert.assertTrue(MySQLConnector.isTransientException(clazz, new MySQLTransactionRollbackException())); {
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( Assert.assertTrue(
MySQLConnector.isTransientException(clazz, new SQLException("some transient failure", "wtf", 1317)) connector.connectorIsTransientException(new SQLException("some transient failure", "wtf", 1317))
); );
Assert.assertFalse( 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 // this method does not specially handle normal transient exceptions either, since it is not vendor specific
Assert.assertFalse( Assert.assertFalse(
MySQLConnector.isTransientException(clazz, new SQLTransientConnectionException("transient")) connector.connectorIsTransientException(new SQLTransientConnectionException("transient"))
); );
} }
@Test @Test
public void testIsExceptionTransientNoMySqlClazz() 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 // 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( Assert.assertFalse(
MySQLConnector.isTransientException(null, new SQLException("some transient failure", "wtf", 1317)) connector.connectorIsTransientException(new SQLException("some transient failure", "wtf", 1317))
); );
Assert.assertFalse( 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( Assert.assertFalse(
MySQLConnector.isTransientException(null, new SQLTransientConnectionException("transient")) connector.connectorIsTransientException(new SQLTransientConnectionException("transient"))
); );
} }
} }