diff --git a/gateway-ha/pom.xml b/gateway-ha/pom.xml index 82ef03440..e5f1bebc3 100644 --- a/gateway-ha/pom.xml +++ b/gateway-ha/pom.xml @@ -76,6 +76,18 @@ guice + + com.h2database + h2 + 1.4.192 + + + + com.mysql + mysql-connector-j + 9.2.0 + + com.nimbusds nimbus-jose-jwt @@ -89,6 +101,12 @@ jdk11 + + com.oracle.database.jdbc + ojdbc11 + 23.7.0.25.01 + + com.squareup.okhttp3 okhttp @@ -266,23 +284,14 @@ - org.weakref - jmxutils - - - - com.mysql - mysql-connector-j - 9.2.0 - runtime + org.postgresql + postgresql + 42.7.5 - com.oracle.database.jdbc - ojdbc11-production - 23.7.0.25.01 - pom - runtime + org.weakref + jmxutils @@ -314,21 +323,7 @@ runtime - - org.postgresql - postgresql - 42.7.5 - runtime - - - - com.h2database - h2 - 1.4.192 - test - - com.squareup.okhttp3 mockwebserver diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/config/DataStoreConfiguration.java b/gateway-ha/src/main/java/io/trino/gateway/ha/config/DataStoreConfiguration.java index cd04c5335..6989fb1bc 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/config/DataStoreConfiguration.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/config/DataStoreConfiguration.java @@ -13,6 +13,13 @@ */ package io.trino.gateway.ha.config; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Enumeration; + +import static java.util.Objects.requireNonNull; + public class DataStoreConfiguration { private String jdbcUrl; @@ -21,10 +28,15 @@ public class DataStoreConfiguration private String driver; private Integer queryHistoryHoursRetention = 4; private boolean runMigrationsEnabled = true; + private DataStoreType dataStoreType; + + // TODO: Refactor to decouple DataStoreConfiguration from a specific + // database implementation after adopting the Airlift configuration framework (https://github.com/trinodb/trino-gateway/issues/378) + private MySqlConfiguration mySqlConfiguration = new MySqlConfiguration(); public DataStoreConfiguration(String jdbcUrl, String user, String password, String driver, Integer queryHistoryHoursRetention, boolean runMigrationsEnabled) { - this.jdbcUrl = jdbcUrl; + this.jdbcUrl = requireNonNull(jdbcUrl, "jdbc must be set"); this.user = user; this.password = password; this.driver = driver; @@ -93,4 +105,45 @@ public void setRunMigrationsEnabled(boolean runMigrationsEnabled) { this.runMigrationsEnabled = runMigrationsEnabled; } + + public MySqlConfiguration getMySqlConfiguration() + { + return mySqlConfiguration; + } + + public void setMysqlConfiguration(MySqlConfiguration mysqlConfig) + { + this.mySqlConfiguration = mysqlConfig; + } + + public DataStoreType getDataStoreType() + { + if (dataStoreType != null) { + return dataStoreType; + } + String jdbcUrl = getJdbcUrl(); + try { + Enumeration drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) { + Driver driver = drivers.nextElement(); + if (driver.acceptsURL(jdbcUrl)) { + for (DataStoreType dataStoreType : DataStoreType.values()) { + if (dataStoreType.getDriverClass().equals(driver.getClass())) { + return dataStoreType; + } + } + break; + } + } + } + catch (SQLException e) { + throw new RuntimeException("Error enumerating JDBC drivers", e); + } + throw new IllegalStateException("Unable to infer DataStoreType for URL: " + jdbcUrl); + } + + public void setDataStoreType(DataStoreType backendType) + { + this.dataStoreType = backendType; + } } diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/config/DataStoreType.java b/gateway-ha/src/main/java/io/trino/gateway/ha/config/DataStoreType.java new file mode 100644 index 000000000..fdff99836 --- /dev/null +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/config/DataStoreType.java @@ -0,0 +1,38 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.gateway.ha.config; + +import java.sql.Driver; + +public enum DataStoreType { + ORACLE(oracle.jdbc.driver.OracleDriver.class), + MYSQL(com.mysql.cj.jdbc.Driver.class), + POSTGRES(org.postgresql.Driver.class), + H2(org.h2.Driver.class); + + private final Class driverClass; + + DataStoreType(Class driverClass) + { + this.driverClass = driverClass; + } + + /** + * Returns the JDBC Driver class associated with this data store type. + */ + public Class getDriverClass() + { + return driverClass; + } +} diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/config/MySqlConfiguration.java b/gateway-ha/src/main/java/io/trino/gateway/ha/config/MySqlConfiguration.java new file mode 100644 index 000000000..5eb2d6aea --- /dev/null +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/config/MySqlConfiguration.java @@ -0,0 +1,95 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.gateway.ha.config; + +import com.mysql.cj.conf.PropertyDefinitions.SslMode; + +/** + * Configuration for MySQL SSL (client cert and truststore settings). + */ +public class MySqlConfiguration +{ + private SslMode sslMode = SslMode.DISABLED; + private String clientCertificateKeyStoreUrl; + private String clientCertificateKeyStorePassword; + private String clientCertificateKeyStoreType; + private String trustCertificateKeyStoreUrl; + private String trustCertificateKeyStorePassword; + + public SslMode getSslMode() + { + return sslMode; + } + + public MySqlConfiguration setSslMode(SslMode sslMode) + { + this.sslMode = sslMode; + return this; + } + + public String getClientCertificateKeyStoreUrl() + { + return clientCertificateKeyStoreUrl; + } + + public MySqlConfiguration setClientCertificateKeyStoreUrl(String url) + { + this.clientCertificateKeyStoreUrl = url; + return this; + } + + public String getClientCertificateKeyStorePassword() + { + return clientCertificateKeyStorePassword; + } + + public MySqlConfiguration setClientCertificateKeyStorePassword(String password) + { + this.clientCertificateKeyStorePassword = password; + return this; + } + + public String getClientCertificateKeyStoreType() + { + return clientCertificateKeyStoreType; + } + + public MySqlConfiguration setClientCertificateKeyStoreType(String type) + { + this.clientCertificateKeyStoreType = type; + return this; + } + + public String getTrustCertificateKeyStoreUrl() + { + return trustCertificateKeyStoreUrl; + } + + public MySqlConfiguration setTrustCertificateKeyStoreUrl(String url) + { + this.trustCertificateKeyStoreUrl = url; + return this; + } + + public String getTrustCertificateKeyStorePassword() + { + return trustCertificateKeyStorePassword; + } + + public MySqlConfiguration setTrustCertificateKeyStorePassword(String password) + { + this.trustCertificateKeyStorePassword = password; + return this; + } +} diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/module/QueryCountBasedRouterProvider.java b/gateway-ha/src/main/java/io/trino/gateway/ha/module/QueryCountBasedRouterProvider.java index 977911405..4ff8ef03d 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/module/QueryCountBasedRouterProvider.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/module/QueryCountBasedRouterProvider.java @@ -14,24 +14,28 @@ package io.trino.gateway.ha.module; import com.google.inject.Provides; +import com.google.inject.Singleton; import io.trino.gateway.ha.config.HaGatewayConfiguration; +import io.trino.gateway.ha.router.GatewayBackendManager; import io.trino.gateway.ha.router.QueryCountBasedRouter; +import io.trino.gateway.ha.router.QueryHistoryManager; import io.trino.gateway.ha.router.RoutingManager; +import static java.util.Objects.requireNonNull; + public class QueryCountBasedRouterProvider extends RouterBaseModule { - private final QueryCountBasedRouter routingManager; - - public QueryCountBasedRouterProvider(HaGatewayConfiguration configuration) - { - super(configuration); - routingManager = new QueryCountBasedRouter(gatewayBackendManager, queryHistoryManager); - } + public QueryCountBasedRouterProvider(HaGatewayConfiguration configuration) {} @Provides - public RoutingManager getRoutingManager() + @Singleton + public RoutingManager provideRoutingManager( + GatewayBackendManager gatewayBackendManager, + QueryHistoryManager queryHistoryManager) { - return this.routingManager; + return new QueryCountBasedRouter( + requireNonNull(gatewayBackendManager, "gatewayBackendManager is null"), + requireNonNull(queryHistoryManager, "queryHistoryManager is null")); } } diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/module/RouterBaseModule.java b/gateway-ha/src/main/java/io/trino/gateway/ha/module/RouterBaseModule.java index 3903e252b..6983a0cc5 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/module/RouterBaseModule.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/module/RouterBaseModule.java @@ -15,8 +15,15 @@ import com.google.inject.AbstractModule; import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import io.trino.gateway.ha.config.DataStoreConfiguration; import io.trino.gateway.ha.config.HaGatewayConfiguration; +import io.trino.gateway.ha.persistence.DefaultJdbcPropertiesProvider; import io.trino.gateway.ha.persistence.JdbcConnectionManager; +import io.trino.gateway.ha.persistence.JdbcPropertiesProvider; +import io.trino.gateway.ha.persistence.JdbcPropertiesProviderFactory; +import io.trino.gateway.ha.persistence.MySqlJdbcPropertiesProvider; import io.trino.gateway.ha.router.GatewayBackendManager; import io.trino.gateway.ha.router.HaGatewayManager; import io.trino.gateway.ha.router.HaQueryHistoryManager; @@ -24,45 +31,57 @@ import io.trino.gateway.ha.router.QueryHistoryManager; import io.trino.gateway.ha.router.ResourceGroupsManager; import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.sqlobject.SqlObjectPlugin; + +import java.util.List; +import java.util.Properties; public class RouterBaseModule extends AbstractModule { - final ResourceGroupsManager resourceGroupsManager; - final GatewayBackendManager gatewayBackendManager; - final QueryHistoryManager queryHistoryManager; - final JdbcConnectionManager connectionManager; - - public RouterBaseModule(HaGatewayConfiguration configuration) + @Override + protected void configure() { - Jdbi jdbi = Jdbi.create(configuration.getDataStore().getJdbcUrl(), configuration.getDataStore().getUser(), configuration.getDataStore().getPassword()); - connectionManager = new JdbcConnectionManager(jdbi, configuration.getDataStore()); - resourceGroupsManager = new HaResourceGroupsManager(connectionManager); - gatewayBackendManager = new HaGatewayManager(jdbi); - queryHistoryManager = new HaQueryHistoryManager(jdbi, configuration.getDataStore().getJdbcUrl().startsWith("jdbc:oracle")); - } + bind(ResourceGroupsManager.class).to(HaResourceGroupsManager.class); + bind(GatewayBackendManager.class).to(HaGatewayManager.class); + bind(QueryHistoryManager.class).to(HaQueryHistoryManager.class); - @Provides - public JdbcConnectionManager getConnectionManager() - { - return this.connectionManager; + bind(new TypeLiteral>() {}).toInstance(List.of( + new MySqlJdbcPropertiesProvider(), + new DefaultJdbcPropertiesProvider())); + + bind(JdbcPropertiesProviderFactory.class).in(Singleton.class); } @Provides - public ResourceGroupsManager getResourceGroupsManager() + @Singleton + public DataStoreConfiguration provideDataStoreConfiguration( + HaGatewayConfiguration configuration) { - return this.resourceGroupsManager; + return configuration.getDataStore(); } @Provides - public GatewayBackendManager getGatewayBackendManager() + @Singleton + public Jdbi provideJdbi( + DataStoreConfiguration configuration, + JdbcPropertiesProviderFactory providerFactory) { - return this.gatewayBackendManager; + Properties properties = providerFactory + .forConfig(configuration) + .getProperties(configuration); + Jdbi jdbi = Jdbi.create(configuration.getJdbcUrl(), properties); + jdbi.installPlugin(new SqlObjectPlugin()); + return jdbi; } @Provides - public QueryHistoryManager getQueryHistoryManager() + @Singleton + public JdbcConnectionManager provideConnectionManager( + Jdbi jdbi, + DataStoreConfiguration configuration, + JdbcPropertiesProviderFactory providerFactory) { - return this.queryHistoryManager; + return new JdbcConnectionManager(jdbi, configuration, providerFactory.forConfig(configuration)); } } diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/module/StochasticRoutingManagerProvider.java b/gateway-ha/src/main/java/io/trino/gateway/ha/module/StochasticRoutingManagerProvider.java index 2949c7fdf..1b224997f 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/module/StochasticRoutingManagerProvider.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/module/StochasticRoutingManagerProvider.java @@ -14,30 +14,31 @@ package io.trino.gateway.ha.module; import com.google.inject.Provides; +import com.google.inject.Singleton; import io.trino.gateway.ha.config.HaGatewayConfiguration; +import io.trino.gateway.ha.router.GatewayBackendManager; +import io.trino.gateway.ha.router.QueryHistoryManager; import io.trino.gateway.ha.router.RoutingManager; import io.trino.gateway.ha.router.StochasticRoutingManager; public class StochasticRoutingManagerProvider extends RouterBaseModule { - private final StochasticRoutingManager routingManager; - - public StochasticRoutingManagerProvider(HaGatewayConfiguration configuration) - { - super(configuration); - routingManager = new StochasticRoutingManager(gatewayBackendManager, queryHistoryManager); - } + public StochasticRoutingManagerProvider(HaGatewayConfiguration configuration) {} @Provides - public StochasticRoutingManager getHaRoutingManager() + @Singleton + public StochasticRoutingManager provideStochasticRoutingManager( + GatewayBackendManager gatewayBackendManager, + QueryHistoryManager queryHistoryManager) { - return this.routingManager; + return new StochasticRoutingManager(gatewayBackendManager, queryHistoryManager); } @Provides - public RoutingManager getRoutingManager() + @Singleton + public RoutingManager provideRoutingManager(StochasticRoutingManager routingManager) { - return getHaRoutingManager(); + return routingManager; } } diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/DefaultJdbcPropertiesProvider.java b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/DefaultJdbcPropertiesProvider.java new file mode 100644 index 000000000..39b984262 --- /dev/null +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/DefaultJdbcPropertiesProvider.java @@ -0,0 +1,47 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.gateway.ha.persistence; + +import io.trino.gateway.ha.config.DataStoreConfiguration; + +import java.util.Properties; + +/** + * Default JDBC properties provider used as a fallback when no database-specific + * {@link JdbcPropertiesProvider} supports the given {@link DataStoreConfiguration}. + * + *

This provider simply sets the basic "user" and "password" properties + * and should always be the last provider in the list of available providers. + * + *

If a more specific provider (e.g., for MySQL, Oracle, etc.) supports the configuration, + * it should be preferred over this basic fallback. + */ +public class DefaultJdbcPropertiesProvider + implements JdbcPropertiesProvider +{ + @Override + public boolean supports(DataStoreConfiguration configuration) + { + return true; + } + + @Override + public Properties getProperties(DataStoreConfiguration configuration) + { + Properties properties = new Properties(); + properties.setProperty("user", configuration.getUser()); + properties.setProperty("password", configuration.getPassword()); + return properties; + } +} diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcConnectionManager.java b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcConnectionManager.java index c1e7b6f29..b9c81c977 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcConnectionManager.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcConnectionManager.java @@ -13,6 +13,8 @@ */ package io.trino.gateway.ha.persistence; +import com.google.inject.Inject; +import com.google.inject.Singleton; import io.airlift.log.Logger; import io.trino.gateway.ha.config.DataStoreConfiguration; import io.trino.gateway.ha.persistence.dao.QueryHistoryDao; @@ -20,27 +22,32 @@ import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.sqlobject.SqlObjectPlugin; +import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static java.util.Objects.requireNonNull; +@Singleton public class JdbcConnectionManager { private static final Logger log = Logger.get(JdbcConnectionManager.class); private final Jdbi jdbi; private final DataStoreConfiguration configuration; + private final JdbcPropertiesProvider jdbcPropertiesProvider; private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - public JdbcConnectionManager(Jdbi jdbi, DataStoreConfiguration configuration) + @Inject + public JdbcConnectionManager(Jdbi jdbi, DataStoreConfiguration configuration, JdbcPropertiesProvider jdbcPropertiesProvider) { this.jdbi = requireNonNull(jdbi, "jdbi is null") .installPlugin(new SqlObjectPlugin()) .registerRowMapper(new RecordAndAnnotatedConstructorMapper()); this.configuration = configuration; + this.jdbcPropertiesProvider = requireNonNull(jdbcPropertiesProvider, "jdbcPropertiesProvider is null"); startCleanUps(); } @@ -54,12 +61,18 @@ public Jdbi getJdbi(@Nullable String routingGroupDatabase) if (routingGroupDatabase == null) { return jdbi; } + Properties properties = jdbcPropertiesProvider.getProperties(configuration); - return Jdbi.create(buildJdbcUrl(routingGroupDatabase), configuration.getUser(), configuration.getPassword()) + return Jdbi.create(buildJdbcUrl(routingGroupDatabase), properties) .installPlugin(new SqlObjectPlugin()) .registerRowMapper(new RecordAndAnnotatedConstructorMapper()); } + public DataStoreConfiguration getConfiguration() + { + return configuration; + } + private String buildJdbcUrl(@Nullable String routingGroupDatabase) { String jdbcUrl = configuration.getJdbcUrl(); diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcPropertiesProvider.java b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcPropertiesProvider.java new file mode 100644 index 000000000..878f2d503 --- /dev/null +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcPropertiesProvider.java @@ -0,0 +1,29 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.gateway.ha.persistence; + +import io.trino.gateway.ha.config.DataStoreConfiguration; + +import java.util.Properties; + +/** + * Given a DataStoreConfiguration, produce the proper JDBC driver properties. + * Different implementations can handle MySQL, Postgres, H2, etc. + */ +public interface JdbcPropertiesProvider +{ + boolean supports(DataStoreConfiguration config); + + Properties getProperties(DataStoreConfiguration config); +} diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcPropertiesProviderFactory.java b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcPropertiesProviderFactory.java new file mode 100644 index 000000000..aa2105383 --- /dev/null +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/JdbcPropertiesProviderFactory.java @@ -0,0 +1,45 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.gateway.ha.persistence; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import io.trino.gateway.ha.config.DataStoreConfiguration; + +import java.util.List; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +@Singleton +public class JdbcPropertiesProviderFactory +{ + private final List providers; + + @Inject + public JdbcPropertiesProviderFactory(List orderedProviders) + { + this.providers = ImmutableList.copyOf(requireNonNull(orderedProviders, "providers is null")); + } + + public JdbcPropertiesProvider forConfig(DataStoreConfiguration config) + { + return providers.stream() + .filter(provider -> provider.supports(config)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( + format("No JdbcPropertiesProvider for driver: %s", config.getDriver()))); + } +} diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/MySqlJdbcPropertiesProvider.java b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/MySqlJdbcPropertiesProvider.java new file mode 100644 index 000000000..3710b5039 --- /dev/null +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/persistence/MySqlJdbcPropertiesProvider.java @@ -0,0 +1,75 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.gateway.ha.persistence; + +import com.mysql.cj.conf.PropertyDefinitions.SslMode; +import com.mysql.cj.conf.PropertyKey; +import io.trino.gateway.ha.config.DataStoreConfiguration; +import io.trino.gateway.ha.config.MySqlConfiguration; + +import java.util.Locale; +import java.util.Properties; + +import static java.util.Objects.requireNonNull; + +public class MySqlJdbcPropertiesProvider + implements JdbcPropertiesProvider +{ + @Override + public boolean supports(DataStoreConfiguration configuration) + { + return configuration.getDriver() != null + && configuration.getJdbcUrl() != null + && configuration.getJdbcUrl().toLowerCase(Locale.ROOT).startsWith("jdbc:mysql"); + } + + @Override + public Properties getProperties(DataStoreConfiguration configuration) + { + Properties properties = new Properties(); + properties.setProperty("user", configuration.getUser()); + + MySqlConfiguration mySqlConfiguration = configuration.getMySqlConfiguration(); + properties.setProperty(PropertyKey.sslMode.getKeyName(), mySqlConfiguration.getSslMode().toString()); + + if (SslMode.VERIFY_CA.equals(mySqlConfiguration.getSslMode())) { + requireNonNull(mySqlConfiguration.getClientCertificateKeyStoreUrl(), + "clientCertificateKeyStoreUrl must be set when sslMode=VERIFY_CA"); + requireNonNull(mySqlConfiguration.getClientCertificateKeyStorePassword(), + "clientCertificateKeyStorePassword must be set when sslMode=VERIFY_CA"); + requireNonNull(mySqlConfiguration.getTrustCertificateKeyStoreUrl(), + "trustCertificateKeyStoreUrl must be set when sslMode=VERIFY_CA"); + requireNonNull(mySqlConfiguration.getTrustCertificateKeyStorePassword(), + "trustCertificateKeyStorePassword must be set when sslMode=VERIFY_CA"); + + properties.setProperty(PropertyKey.clientCertificateKeyStoreUrl.getKeyName(), + mySqlConfiguration.getClientCertificateKeyStoreUrl()); + properties.setProperty(PropertyKey.clientCertificateKeyStorePassword.getKeyName(), + mySqlConfiguration.getClientCertificateKeyStorePassword()); + if (mySqlConfiguration.getClientCertificateKeyStoreType() != null) { + properties.setProperty( + PropertyKey.clientCertificateKeyStoreType.getKeyName(), + mySqlConfiguration.getClientCertificateKeyStoreType()); + } + properties.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), + mySqlConfiguration.getTrustCertificateKeyStoreUrl()); + properties.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), + mySqlConfiguration.getTrustCertificateKeyStorePassword()); + } + else { + properties.setProperty("password", configuration.getPassword()); + } + return properties; + } +} diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaGatewayManager.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaGatewayManager.java index 732b06912..13f1bc3ca 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaGatewayManager.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaGatewayManager.java @@ -14,6 +14,7 @@ package io.trino.gateway.ha.router; import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; import io.airlift.log.Logger; import io.trino.gateway.ha.config.ProxyBackendConfiguration; import io.trino.gateway.ha.persistence.dao.GatewayBackend; @@ -33,6 +34,7 @@ public class HaGatewayManager private final GatewayBackendDao dao; + @Inject public HaGatewayManager(Jdbi jdbi) { dao = requireNonNull(jdbi, "jdbi is null").onDemand(GatewayBackendDao.class); diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java index a25ed0509..310fe864f 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java @@ -14,6 +14,9 @@ package io.trino.gateway.ha.router; import com.google.common.base.Strings; +import com.google.inject.Inject; +import io.trino.gateway.ha.config.DataStoreConfiguration; +import io.trino.gateway.ha.config.DataStoreType; import io.trino.gateway.ha.domain.TableData; import io.trino.gateway.ha.domain.request.QueryHistoryRequest; import io.trino.gateway.ha.domain.response.DistributionResponse; @@ -38,12 +41,14 @@ public class HaQueryHistoryManager private static final int FIRST_PAGE_NO = 1; private final QueryHistoryDao dao; - private final boolean isOracleBackend; + private final DataStoreType backend; - public HaQueryHistoryManager(Jdbi jdbi, boolean isOracleBackend) + @Inject + public HaQueryHistoryManager(Jdbi jdbi, DataStoreConfiguration configuration) { + requireNonNull(configuration, "DataStoreConfiguration is null"); dao = requireNonNull(jdbi, "jdbi is null").onDemand(QueryHistoryDao.class); - this.isOracleBackend = isOracleBackend; + this.backend = configuration.getDataStoreType(); } @Override @@ -69,10 +74,10 @@ public List fetchQueryHistory(Optional user) { List histories; if (user.isPresent()) { - histories = dao.findRecentQueriesByUserName(user.orElseThrow(), isOracleBackend); + histories = dao.findRecentQueriesByUserName(user.orElseThrow(), backend == DataStoreType.ORACLE); } else { - histories = dao.findRecentQueries(isOracleBackend); + histories = dao.findRecentQueries(backend == DataStoreType.ORACLE); } return upcast(histories); } diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaResourceGroupsManager.java b/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaResourceGroupsManager.java index 7bbc83b35..79ca18cbc 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaResourceGroupsManager.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/router/HaResourceGroupsManager.java @@ -14,6 +14,7 @@ package io.trino.gateway.ha.router; import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; import io.trino.gateway.ha.persistence.JdbcConnectionManager; import io.trino.gateway.ha.persistence.dao.ExactMatchSourceSelectors; import io.trino.gateway.ha.persistence.dao.ExactMatchSourceSelectorsDao; @@ -36,6 +37,7 @@ public class HaResourceGroupsManager private final JdbcConnectionManager connectionManager; private final ExactMatchSourceSelectorsDao exactMatchSourceSelectorsDao; + @Inject public HaResourceGroupsManager(JdbcConnectionManager connectionManager) { this.connectionManager = connectionManager; diff --git a/gateway-ha/src/test/java/io/trino/gateway/ha/TestTrinoResource.java b/gateway-ha/src/test/java/io/trino/gateway/ha/TestTrinoResource.java index 99a00dcb6..b81a83a22 100644 --- a/gateway-ha/src/test/java/io/trino/gateway/ha/TestTrinoResource.java +++ b/gateway-ha/src/test/java/io/trino/gateway/ha/TestTrinoResource.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.trino.gateway.ha.config.DataStoreConfiguration; +import io.trino.gateway.ha.persistence.DefaultJdbcPropertiesProvider; import io.trino.gateway.ha.persistence.JdbcConnectionManager; import io.trino.gateway.ha.router.HaResourceGroupsManager; import okhttp3.MediaType; @@ -66,7 +67,7 @@ void setup() // Setup resource group manager DataStoreConfiguration db = new DataStoreConfiguration(postgresql.getJdbcUrl(), postgresql.getUsername(), postgresql.getPassword(), "org.postgresql.Driver", 4, true); Jdbi jdbi = Jdbi.create(postgresql.getJdbcUrl(), postgresql.getUsername(), postgresql.getPassword()); - connectionManager = new JdbcConnectionManager(jdbi, db); + connectionManager = new JdbcConnectionManager(jdbi, db, new DefaultJdbcPropertiesProvider()); resourceGroupManager = new HaResourceGroupsManager(connectionManager); // Start Trino Gateway so migrations are run to create tables before inserting test data diff --git a/gateway-ha/src/test/java/io/trino/gateway/ha/TestingJdbcConnectionManager.java b/gateway-ha/src/test/java/io/trino/gateway/ha/TestingJdbcConnectionManager.java index 1f60a0dfb..d0f0802a6 100644 --- a/gateway-ha/src/test/java/io/trino/gateway/ha/TestingJdbcConnectionManager.java +++ b/gateway-ha/src/test/java/io/trino/gateway/ha/TestingJdbcConnectionManager.java @@ -14,6 +14,7 @@ package io.trino.gateway.ha; import io.trino.gateway.ha.config.DataStoreConfiguration; +import io.trino.gateway.ha.persistence.DefaultJdbcPropertiesProvider; import io.trino.gateway.ha.persistence.JdbcConnectionManager; import org.jdbi.v3.core.Jdbi; import org.testcontainers.containers.JdbcDatabaseContainer; @@ -33,12 +34,12 @@ public static JdbcConnectionManager createTestingJdbcConnectionManager() HaGatewayTestUtils.seedRequiredData(tempH2DbDir.getAbsolutePath()); DataStoreConfiguration db = new DataStoreConfiguration(jdbcUrl, "sa", "sa", "org.h2.Driver", 4, false); Jdbi jdbi = Jdbi.create(jdbcUrl, "sa", "sa"); - return new JdbcConnectionManager(jdbi, db); + return new JdbcConnectionManager(jdbi, db, new DefaultJdbcPropertiesProvider()); } public static JdbcConnectionManager createTestingJdbcConnectionManager(JdbcDatabaseContainer container, DataStoreConfiguration config) { Jdbi jdbi = Jdbi.create(container.getJdbcUrl(), container.getUsername(), container.getPassword()); - return new JdbcConnectionManager(jdbi, config); + return new JdbcConnectionManager(jdbi, config, new DefaultJdbcPropertiesProvider()); } } diff --git a/gateway-ha/src/test/java/io/trino/gateway/ha/persistence/TestJdbcPropertiesProviderFactory.java b/gateway-ha/src/test/java/io/trino/gateway/ha/persistence/TestJdbcPropertiesProviderFactory.java new file mode 100644 index 000000000..537419983 --- /dev/null +++ b/gateway-ha/src/test/java/io/trino/gateway/ha/persistence/TestJdbcPropertiesProviderFactory.java @@ -0,0 +1,127 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.gateway.ha.persistence; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; +import com.mysql.cj.conf.PropertyKey; +import io.trino.gateway.ha.config.DataStoreConfiguration; +import io.trino.gateway.ha.config.HaGatewayConfiguration; +import io.trino.gateway.ha.config.MySqlConfiguration; +import io.trino.gateway.ha.module.RouterBaseModule; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + +final class TestJdbcPropertiesProviderFactory +{ + private static JdbcPropertiesProviderFactory factoryFor(DataStoreConfiguration dbConfig) + { + HaGatewayConfiguration haConfig = new HaGatewayConfiguration(); + haConfig.setDataStore(dbConfig); + + Injector injector = Guice.createInjector( + binder -> binder.bind(HaGatewayConfiguration.class) + .toInstance(haConfig), + new RouterBaseModule()); + return injector.getInstance(JdbcPropertiesProviderFactory.class); + } + + private DataStoreConfiguration makeMySqlConfig(SslMode mode) + { + DataStoreConfiguration db = new DataStoreConfiguration(); + db.setDriver("com.mysql.Driver"); + db.setJdbcUrl("jdbc:mysql://host/db"); + db.setUser("root"); + db.setPassword("root123"); + MySqlConfiguration mySqlConfiguration = db.getMySqlConfiguration(); + mySqlConfiguration.setSslMode(mode); + return db; + } + + private DataStoreConfiguration makeH2Config() + { + DataStoreConfiguration db = new DataStoreConfiguration(); + db.setDriver("org.h2.Driver"); + db.setJdbcUrl("jdbc:h2:mem:test"); + db.setUser("sa"); + db.setPassword("sa"); + return db; + } + + @Test + void testBasicProviderWhenH2() + { + DataStoreConfiguration cfg = makeH2Config(); + JdbcPropertiesProviderFactory factory = factoryFor(cfg); + DefaultJdbcPropertiesProvider provider = (DefaultJdbcPropertiesProvider) factory.forConfig(cfg); + assertThat(provider).isInstanceOf(DefaultJdbcPropertiesProvider.class); + + Properties properties = provider.getProperties(cfg); + assertThat(properties) + .hasSize(2) + .containsEntry("user", "sa") + .containsEntry("password", "sa"); + } + + @Test + void testMysqlProviderWhenSslDisabled() + { + DataStoreConfiguration cfg = makeMySqlConfig(SslMode.DISABLED); + JdbcPropertiesProviderFactory factory = factoryFor(cfg); + JdbcPropertiesProvider provider = factory.forConfig(cfg); + assertThat(provider).isInstanceOf(MySqlJdbcPropertiesProvider.class); + + Properties properties = provider.getProperties(cfg); + assertThat(properties.getProperty("user")).isEqualTo("root"); + assertThat(properties.getProperty("password")).isEqualTo("root123"); + assertThat(properties.getProperty(PropertyKey.sslMode.getKeyName())) + .isEqualTo("DISABLED"); + + assertThat(properties).doesNotContainKeys( + PropertyKey.clientCertificateKeyStoreUrl.getKeyName(), + PropertyKey.clientCertificateKeyStorePassword.getKeyName(), + PropertyKey.clientCertificateKeyStoreType.getKeyName(), + PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), + PropertyKey.trustCertificateKeyStorePassword.getKeyName()); + } + + @Test + void testMysqlProviderWhenVerifyCa() + { + DataStoreConfiguration cfg = makeMySqlConfig(SslMode.VERIFY_CA); + JdbcPropertiesProviderFactory factory = factoryFor(cfg); + MySqlConfiguration mysqlConfig = cfg.getMySqlConfiguration(); + mysqlConfig.setClientCertificateKeyStoreUrl("file:/tmp/cli.p12") + .setClientCertificateKeyStorePassword("cpw") + .setClientCertificateKeyStoreType("PKCS12") + .setTrustCertificateKeyStoreUrl("file:/tmp/trs.p12") + .setTrustCertificateKeyStorePassword("tpw"); + + JdbcPropertiesProvider provider = factory.forConfig(cfg); + assertThat(provider).isInstanceOf(MySqlJdbcPropertiesProvider.class); + + Properties properties = provider.getProperties(cfg); + assertThat(properties.getProperty("user")).isEqualTo("root"); + assertThat(properties.getProperty(PropertyKey.sslMode.getKeyName())) + .isEqualTo("VERIFY_CA"); + assertThat(properties.getProperty(PropertyKey.clientCertificateKeyStoreUrl.getKeyName())) + .isEqualTo("file:/tmp/cli.p12"); + assertThat(properties.getProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName())) + .isEqualTo("file:/tmp/trs.p12"); + } +} diff --git a/gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java b/gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java index 3f5de4bbc..640577341 100644 --- a/gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java +++ b/gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java @@ -50,7 +50,7 @@ void setUp() true); FlywayMigration.migrate(config); JdbcConnectionManager jdbcConnectionManager = createTestingJdbcConnectionManager(container, config); - queryHistoryManager = new HaQueryHistoryManager(jdbcConnectionManager.getJdbi(), container.getJdbcUrl().startsWith("jdbc:oracle")); + queryHistoryManager = new HaQueryHistoryManager(jdbcConnectionManager.getJdbi(), config); } @AfterAll diff --git a/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestSpecificDbResourceGroupsManager.java b/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestSpecificDbResourceGroupsManager.java index f78b80377..4e35a9332 100644 --- a/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestSpecificDbResourceGroupsManager.java +++ b/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestSpecificDbResourceGroupsManager.java @@ -13,10 +13,14 @@ */ package io.trino.gateway.ha.router; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; import io.trino.gateway.ha.HaGatewayTestUtils; import io.trino.gateway.ha.config.DataStoreConfiguration; +import io.trino.gateway.ha.config.HaGatewayConfiguration; +import io.trino.gateway.ha.module.RouterBaseModule; import io.trino.gateway.ha.persistence.JdbcConnectionManager; -import org.jdbi.v3.core.Jdbi; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -47,8 +51,21 @@ void setUp() HaGatewayTestUtils.seedRequiredData(tempH2DbDir.getAbsolutePath()); DataStoreConfiguration db = new DataStoreConfiguration(jdbcUrl, "sa", "sa", "org.h2.Driver", 4, false); - Jdbi jdbi = Jdbi.create(jdbcUrl, "sa", "sa"); - JdbcConnectionManager connectionManager = new JdbcConnectionManager(jdbi, db); + HaGatewayConfiguration configuration = new HaGatewayConfiguration(); + configuration.setDataStore(db); + AbstractModule testConfigModule = new AbstractModule() { + @Override + protected void configure() + { + bind(HaGatewayConfiguration.class) + .toInstance(configuration); + } + }; + Injector injector = Guice.createInjector( + testConfigModule, + new RouterBaseModule()); + + JdbcConnectionManager connectionManager = injector.getInstance(JdbcConnectionManager.class); super.resourceGroupManager = new HaResourceGroupsManager(connectionManager); } diff --git a/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestStochasticRoutingManager.java b/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestStochasticRoutingManager.java index bd7bf67e6..6136b7a01 100644 --- a/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestStochasticRoutingManager.java +++ b/gateway-ha/src/test/java/io/trino/gateway/ha/router/TestStochasticRoutingManager.java @@ -36,7 +36,7 @@ void setUp() { JdbcConnectionManager connectionManager = createTestingJdbcConnectionManager(); backendManager = new HaGatewayManager(connectionManager.getJdbi()); - historyManager = new HaQueryHistoryManager(connectionManager.getJdbi(), false); + historyManager = new HaQueryHistoryManager(connectionManager.getJdbi(), connectionManager.getConfiguration()); haRoutingManager = new StochasticRoutingManager(backendManager, historyManager); }