From 978b8f7ddee7fcad822975e93d053779f69591ef Mon Sep 17 00:00:00 2001 From: Clint Wylie Date: Mon, 31 Jan 2022 19:36:24 -0800 Subject: [PATCH] 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. --- .../storage/mysql/MySQLConnector.java | 40 +++++++------- .../storage/mysql/MySQLConnectorTest.java | 53 ++++++++++++++----- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java b/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java index 4cd2dd10845..d4eb57bb818 100644 --- a/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java +++ b/extensions-core/mysql-metadata-storage/src/main/java/org/apache/druid/metadata/storage/mysql/MySQLConnector.java @@ -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; - } } diff --git a/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java b/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java index d33931420ca..fceefe6e493 100644 --- a/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java +++ b/extensions-core/mysql-metadata-storage/src/test/java/org/apache/druid/metadata/storage/mysql/MySQLConnectorTest.java @@ -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 CONNECTOR_CONFIG_SUPPLIER = + MetadataStorageConnectorConfig::new; + private static final Supplier 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")) ); } }