From 3980b353ba7d2054ee04a9194136b66a3dd30f6f Mon Sep 17 00:00:00 2001 From: Thomas Segismont Date: Tue, 21 Mar 2023 22:11:31 +0100 Subject: [PATCH] Dynamic pool configuration for Oracle Client --- .../client/ChangingCredentialsProvider.java | 36 +++++++++ .../client/ChangingCredentialsTest.java | 38 ++++++++++ .../ChangingCredentialsTestResource.java | 41 ++++++++++ ...pplication-changing-credentials.properties | 5 ++ .../client/runtime/OraclePoolRecorder.java | 74 +++++++++++++------ 5 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsProvider.java create mode 100644 extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTest.java create mode 100644 extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTestResource.java create mode 100644 extensions/reactive-oracle-client/deployment/src/test/resources/application-changing-credentials.properties diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsProvider.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsProvider.java new file mode 100644 index 0000000000000..bf9997072859d --- /dev/null +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsProvider.java @@ -0,0 +1,36 @@ +package io.quarkus.reactive.oracle.client; + +import java.util.HashMap; +import java.util.Map; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.logging.Logger; + +import io.quarkus.credentials.CredentialsProvider; + +@ApplicationScoped +public class ChangingCredentialsProvider implements CredentialsProvider { + + private static final Logger log = Logger.getLogger(ChangingCredentialsProvider.class.getName()); + + private volatile Map properties; + + public ChangingCredentialsProvider() { + properties = new HashMap<>(); + properties.put(USER_PROPERTY_NAME, "SYSTEM"); + properties.put(PASSWORD_PROPERTY_NAME, "hibernate_orm_test"); + } + + public void changeProperties() { + properties = new HashMap<>(); + properties.put(USER_PROPERTY_NAME, "user2"); + properties.put(PASSWORD_PROPERTY_NAME, "user2"); + } + + @Override + public Map getCredentials(String credentialsProviderName) { + log.info("credentials provider returning " + properties); + return properties; + } +} diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTest.java new file mode 100644 index 0000000000000..68370486a6aa9 --- /dev/null +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTest.java @@ -0,0 +1,38 @@ +package io.quarkus.reactive.oracle.client; + +import static io.restassured.RestAssured.given; +import static java.util.concurrent.TimeUnit.SECONDS; + +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ChangingCredentialsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(ChangingCredentialsProvider.class) + .addClass(ChangingCredentialsTestResource.class) + .addAsResource("application-changing-credentials.properties", "application.properties")); + + @Test + public void testConnect() throws Exception { + given() + .when().get("/test") + .then() + .statusCode(200) + .body(CoreMatchers.equalTo("SYSTEM")); + + SECONDS.sleep(2); // sleep longer than pool idle connection timeout + + given() + .when().get("/test") + .then() + .statusCode(200) + .body(CoreMatchers.equalTo("USER2")); + } + +} diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTestResource.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTestResource.java new file mode 100644 index 0000000000000..b1c172ad91c07 --- /dev/null +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTestResource.java @@ -0,0 +1,41 @@ +package io.quarkus.reactive.oracle.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.oracleclient.OraclePool; + +@Path("/test") +public class ChangingCredentialsTestResource { + + @Inject + OraclePool client; + + @Inject + ChangingCredentialsProvider credentialsProvider; + + @PostConstruct + void addUser() { + client.query("CREATE USER user2 IDENTIFIED BY user2").executeAndForget(); + client.query("GRANT CREATE SESSION TO user2").executeAndForget(); + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Uni connect() { + return client.query("SELECT USER FROM DUAL").execute() + .map(rowSet -> { + assertEquals(1, rowSet.size()); + return Response.ok(rowSet.iterator().next().getString(0)).build(); + }).eventually(credentialsProvider::changeProperties); + } + +} diff --git a/extensions/reactive-oracle-client/deployment/src/test/resources/application-changing-credentials.properties b/extensions/reactive-oracle-client/deployment/src/test/resources/application-changing-credentials.properties new file mode 100644 index 0000000000000..43e17fcf6bce7 --- /dev/null +++ b/extensions/reactive-oracle-client/deployment/src/test/resources/application-changing-credentials.properties @@ -0,0 +1,5 @@ +quarkus.datasource.db-kind=oracle +quarkus.datasource.credentials-provider=changing +quarkus.datasource.reactive.url=${reactive-oracledb.url} +quarkus.datasource.reactive.max-size=1 +quarkus.datasource.reactive.idle-timeout=PT1S diff --git a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java index 853f62aae1327..fa67d1fd05231 100644 --- a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java +++ b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java @@ -3,6 +3,7 @@ import static io.quarkus.credentials.CredentialsProvider.PASSWORD_PROPERTY_NAME; import static io.quarkus.credentials.CredentialsProvider.USER_PROPERTY_NAME; +import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -20,13 +21,19 @@ import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig; +import io.quarkus.reactive.datasource.runtime.PoolCloseFutureFactory; +import io.quarkus.reactive.datasource.runtime.ReactiveDatasourceConnectionProvider; +import io.quarkus.reactive.datasource.runtime.ReactiveDatasourceCredentialsProvider; import io.quarkus.reactive.oracle.client.OraclePoolCreator; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Vertx; +import io.vertx.core.impl.CloseFuture; +import io.vertx.core.impl.VertxInternal; import io.vertx.oracleclient.OracleConnectOptions; import io.vertx.oracleclient.OraclePool; +import io.vertx.oracleclient.spi.OracleDriver; import io.vertx.sqlclient.PoolOptions; @SuppressWarnings("deprecation") @@ -43,7 +50,7 @@ public RuntimeValue configureOraclePool(RuntimeValue vertx, DataSourcesReactiveOracleConfig dataSourcesReactiveOracleConfig, ShutdownContext shutdown) { - OraclePool oraclePool = initialize(vertx.getValue(), + OraclePool oraclePool = initialize((VertxInternal) vertx.getValue(), eventLoopCount.get(), dataSourceName, dataSourcesRuntimeConfig.getDataSourceRuntimeConfig(dataSourceName), @@ -58,7 +65,7 @@ public RuntimeValue mutinyOraclePool(Ru return new RuntimeValue<>(io.vertx.mutiny.oracleclient.OraclePool.newInstance(oraclePool.getValue())); } - private OraclePool initialize(Vertx vertx, + private OraclePool initialize(VertxInternal vertx, Integer eventLoopCount, String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, @@ -68,13 +75,47 @@ private OraclePool initialize(Vertx vertx, dataSourceReactiveOracleConfig); OracleConnectOptions oracleConnectOptions = toOracleConnectOptions(dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveOracleConfig); - // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with oracle. + CloseFuture poolCloseFuture = PoolCloseFutureFactory.create(vertx); + ReactiveDatasourceConnectionProvider connectionProvider = toConnectionProvider(vertx, + oracleConnectOptions, dataSourceRuntimeConfig, poolCloseFuture); // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with oracle. // and the client_name as tag. // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName oracleConnectOptions.setMetricsName("oracle|" + dataSourceName); - return createPool(vertx, poolOptions, oracleConnectOptions, dataSourceName); + return createPool(vertx, poolOptions, oracleConnectOptions, dataSourceName, connectionProvider, poolCloseFuture); + } + + private ReactiveDatasourceConnectionProvider toConnectionProvider(VertxInternal vertx, + OracleConnectOptions oracleConnectOptions, DataSourceRuntimeConfig dataSourceRuntimeConfig, + CloseFuture poolCloseFuture) { + ReactiveDatasourceCredentialsProvider reactiveDatasourceCredentialsProvider; + if (dataSourceRuntimeConfig.credentialsProvider.isPresent()) { + String beanName = dataSourceRuntimeConfig.credentialsProviderName.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); + String name = dataSourceRuntimeConfig.credentialsProvider.get(); + Map credentials = credentialsProvider.getCredentials(name); + String user = credentials.get(USER_PROPERTY_NAME); + String password = credentials.get(PASSWORD_PROPERTY_NAME); + if (user != null) { + oracleConnectOptions.setUser(user); + } + if (password != null) { + oracleConnectOptions.setPassword(password); + } + reactiveDatasourceCredentialsProvider = new ReactiveDatasourceCredentialsProvider(credentialsProvider, name); + } else { + reactiveDatasourceCredentialsProvider = new ReactiveDatasourceCredentialsProvider(new CredentialsProvider() { + @Override + public Map getCredentials(String credentialsProviderName) { + return Map.of(USER_PROPERTY_NAME, oracleConnectOptions.getUser(), PASSWORD_PROPERTY_NAME, + oracleConnectOptions.getPassword()); + } + }, null); + } + + return new ReactiveDatasourceConnectionProvider<>(vertx, OracleDriver.INSTANCE, reactiveDatasourceCredentialsProvider, + oracleConnectOptions, OracleConnectOptions::new, poolCloseFuture); } private PoolOptions toPoolOptions(Integer eventLoopCount, @@ -130,29 +171,14 @@ private OracleConnectOptions toOracleConnectOptions(DataSourceRuntimeConfig data oracleConnectOptions.setPassword(dataSourceRuntimeConfig.password.get()); } - // credentials provider - if (dataSourceRuntimeConfig.credentialsProvider.isPresent()) { - String beanName = dataSourceRuntimeConfig.credentialsProviderName.orElse(null); - CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); - String name = dataSourceRuntimeConfig.credentialsProvider.get(); - Map credentials = credentialsProvider.getCredentials(name); - String user = credentials.get(USER_PROPERTY_NAME); - String password = credentials.get(PASSWORD_PROPERTY_NAME); - if (user != null) { - oracleConnectOptions.setUser(user); - } - if (password != null) { - oracleConnectOptions.setPassword(password); - } - } - dataSourceReactiveRuntimeConfig.additionalProperties.forEach(oracleConnectOptions::addProperty); return oracleConnectOptions; } private OraclePool createPool(Vertx vertx, PoolOptions poolOptions, OracleConnectOptions oracleConnectOptions, - String dataSourceName) { + String dataSourceName, ReactiveDatasourceConnectionProvider connectionProvider, + CloseFuture poolCloseFuture) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { instance = Arc.container().select(OraclePoolCreator.class); @@ -164,7 +190,11 @@ private OraclePool createPool(Vertx vertx, PoolOptions poolOptions, OracleConnec OraclePoolCreator.Input input = new DefaultInput(vertx, poolOptions, oracleConnectOptions); return instance.get().create(input); } - return OraclePool.pool(vertx, oracleConnectOptions, poolOptions); + // Temporarily casting because OracleDriver does not override newPool yet + OraclePool pool = (OraclePool) OracleDriver.INSTANCE + .newPool(vertx, Collections.singletonList(oracleConnectOptions), poolOptions, poolCloseFuture) + .connectionProvider(connectionProvider); + return pool; } private static class DefaultInput implements OraclePoolCreator.Input {