diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 9cee2a325e4f8..8828abc80c45e 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -1878,6 +1878,13 @@ quarkus-reactive-datasource-deployment ${project.version} + + io.quarkus + quarkus-reactive-datasource-deployment + ${project.version} + test + test-jar + io.quarkus quarkus-reactive-db2-client diff --git a/extensions/reactive-datasource/deployment/pom.xml b/extensions/reactive-datasource/deployment/pom.xml index 1933639060b01..241c34489d393 100644 --- a/extensions/reactive-datasource/deployment/pom.xml +++ b/extensions/reactive-datasource/deployment/pom.xml @@ -30,6 +30,11 @@ quarkus-junit5-internal test + + io.rest-assured + rest-assured + test + @@ -46,6 +51,17 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + diff --git a/extensions/reactive-datasource/deployment/src/test/java/io/quarkus/reactive/datasource/ChangingCredentialsProviderBase.java b/extensions/reactive-datasource/deployment/src/test/java/io/quarkus/reactive/datasource/ChangingCredentialsProviderBase.java new file mode 100644 index 0000000000000..de9853d806bc3 --- /dev/null +++ b/extensions/reactive-datasource/deployment/src/test/java/io/quarkus/reactive/datasource/ChangingCredentialsProviderBase.java @@ -0,0 +1,38 @@ +package io.quarkus.reactive.datasource; + +import java.util.HashMap; +import java.util.Map; + +import org.jboss.logging.Logger; + +import io.quarkus.credentials.CredentialsProvider; + +public abstract class ChangingCredentialsProviderBase implements CredentialsProvider { + + private static final Logger log = Logger.getLogger(ChangingCredentialsProviderBase.class.getName()); + + private final String user2; + private final String password2; + + private volatile Map properties; + + protected ChangingCredentialsProviderBase(String user1, String password1, String user2, String password2) { + properties = new HashMap<>(); + properties.put(USER_PROPERTY_NAME, user1); + properties.put(PASSWORD_PROPERTY_NAME, password1); + this.user2 = user2; + this.password2 = password2; + } + + public void changeProperties() { + properties = new HashMap<>(); + properties.put(USER_PROPERTY_NAME, user2); + properties.put(PASSWORD_PROPERTY_NAME, password2); + } + + @Override + public Map getCredentials(String credentialsProviderName) { + log.info("credentials provider returning " + properties); + return properties; + } +} diff --git a/extensions/reactive-datasource/deployment/src/test/java/io/quarkus/reactive/datasource/ChangingCredentialsTestBase.java b/extensions/reactive-datasource/deployment/src/test/java/io/quarkus/reactive/datasource/ChangingCredentialsTestBase.java new file mode 100644 index 0000000000000..c264188fcaefd --- /dev/null +++ b/extensions/reactive-datasource/deployment/src/test/java/io/quarkus/reactive/datasource/ChangingCredentialsTestBase.java @@ -0,0 +1,36 @@ +package io.quarkus.reactive.datasource; + +import static io.restassured.RestAssured.given; +import static java.util.concurrent.TimeUnit.SECONDS; + +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.Test; + +public abstract class ChangingCredentialsTestBase { + + private String user1; + private String user2; + + protected ChangingCredentialsTestBase(String user1, String user2) { + this.user1 = user1; + this.user2 = user2; + } + + @Test + public void testConnect() throws Exception { + given() + .when().get("/test") + .then() + .statusCode(200) + .body(CoreMatchers.equalTo(user1)); + + 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-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/ConnectOptionsSupplier.java b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/ConnectOptionsSupplier.java new file mode 100644 index 0000000000000..a5677b11d0a90 --- /dev/null +++ b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/ConnectOptionsSupplier.java @@ -0,0 +1,67 @@ +package io.quarkus.reactive.datasource.runtime; + +import static io.quarkus.credentials.CredentialsProvider.PASSWORD_PROPERTY_NAME; +import static io.quarkus.credentials.CredentialsProvider.USER_PROPERTY_NAME; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntUnaryOperator; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +import io.quarkus.credentials.CredentialsProvider; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.Promise; +import io.vertx.core.Vertx; +import io.vertx.sqlclient.SqlConnectOptions; + +public class ConnectOptionsSupplier implements Supplier> { + + private final Vertx vertx; + private final CredentialsProvider credentialsProvider; + private final String credentialsProviderName; + private final List connectOptionsList; + private final UnaryOperator connectOptionsCopy; + private final Handler> blockingCodeHandler; + + public ConnectOptionsSupplier(Vertx vertx, CredentialsProvider credentialsProvider, String credentialsProviderName, + List connectOptionsList, UnaryOperator connectOptionsCopy) { + this.vertx = vertx; + this.credentialsProvider = credentialsProvider; + this.credentialsProviderName = credentialsProviderName; + this.connectOptionsList = connectOptionsList; + this.connectOptionsCopy = connectOptionsCopy; + blockingCodeHandler = new BlockingCodeHandler(); + } + + @Override + public Future get() { + return vertx.executeBlocking(blockingCodeHandler, false); + } + + private class BlockingCodeHandler implements Handler>, IntUnaryOperator { + + final AtomicInteger idx = new AtomicInteger(); + + @Override + public void handle(Promise promise) { + Map credentials = credentialsProvider.getCredentials(credentialsProviderName); + String user = credentials.get(USER_PROPERTY_NAME); + String password = credentials.get(PASSWORD_PROPERTY_NAME); + + int nextIdx = idx.getAndUpdate(this); + + CO connectOptions = connectOptionsCopy.apply(connectOptionsList.get(nextIdx)); + connectOptions.setUser(user).setPassword(password); + + promise.complete(connectOptions); + } + + @Override + public int applyAsInt(int previousIdx) { + return previousIdx == connectOptionsList.size() - 1 ? 0 : previousIdx + 1; + } + } +} diff --git a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java index 6c04c5e312cb3..57613bc95dbfb 100644 --- a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java +++ b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java @@ -25,16 +25,20 @@ import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig; import io.quarkus.reactive.db2.client.DB2PoolCreator; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.impl.VertxInternal; import io.vertx.db2client.DB2ConnectOptions; import io.vertx.db2client.DB2Pool; import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.impl.Utils; @Recorder public class DB2PoolRecorder { @@ -49,7 +53,7 @@ public RuntimeValue configureDB2Pool(RuntimeValue vertx, DataSourcesReactiveDB2Config dataSourcesReactiveDB2Config, ShutdownContext shutdown) { - DB2Pool db2Pool = initialize(vertx.getValue(), + DB2Pool db2Pool = initialize((VertxInternal) vertx.getValue(), eventLoopCount.get(), dataSourceName, dataSourcesRuntimeConfig.getDataSourceRuntimeConfig(dataSourceName), @@ -64,7 +68,7 @@ public RuntimeValue mutinyDB2Pool(RuntimeValu return new RuntimeValue<>(io.vertx.mutiny.db2client.DB2Pool.newInstance(db2Pool.getValue())); } - private DB2Pool initialize(Vertx vertx, + private DB2Pool initialize(VertxInternal vertx, Integer eventLoopCount, String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, @@ -72,16 +76,26 @@ private DB2Pool initialize(Vertx vertx, DataSourceReactiveDB2Config dataSourceReactiveDB2Config) { PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveDB2Config); - DB2ConnectOptions connectOptions = toConnectOptions(dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, - dataSourceReactiveDB2Config); - - // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with db2. and - // the client_name as tag. - // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and - // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName - connectOptions.setMetricsName("db2|" + dataSourceName); + DB2ConnectOptions db2ConnectOptions = toConnectOptions(dataSourceName, dataSourceRuntimeConfig, + dataSourceReactiveRuntimeConfig, dataSourceReactiveDB2Config); + Supplier> databasesSupplier = toDatabasesSupplier(vertx, List.of(db2ConnectOptions), + dataSourceRuntimeConfig); + return createPool(vertx, poolOptions, db2ConnectOptions, dataSourceName, databasesSupplier); + } - return createPool(vertx, poolOptions, connectOptions, dataSourceName); + private Supplier> toDatabasesSupplier(Vertx vertx, List db2ConnectOptionsList, + DataSourceRuntimeConfig dataSourceRuntimeConfig) { + Supplier> supplier; + if (dataSourceRuntimeConfig.credentialsProvider.isPresent()) { + String beanName = dataSourceRuntimeConfig.credentialsProviderName.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); + String name = dataSourceRuntimeConfig.credentialsProvider.get(); + supplier = new ConnectOptionsSupplier<>(vertx, credentialsProvider, name, db2ConnectOptionsList, + DB2ConnectOptions::new); + } else { + supplier = Utils.roundRobinSupplier(db2ConnectOptionsList); + } + return supplier; } private PoolOptions toPoolOptions(Integer eventLoopCount, @@ -114,7 +128,7 @@ private PoolOptions toPoolOptions(Integer eventLoopCount, return poolOptions; } - private DB2ConnectOptions toConnectOptions(DataSourceRuntimeConfig dataSourceRuntimeConfig, + private DB2ConnectOptions toConnectOptions(String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactiveDB2Config dataSourceReactiveDB2Config) { DB2ConnectOptions connectOptions; @@ -155,7 +169,7 @@ private DB2ConnectOptions toConnectOptions(DataSourceRuntimeConfig dataSourceRun connectOptions.setUser(user); } if (password != null) { - connectOptions.setPassword(user); + connectOptions.setPassword(password); } } @@ -184,11 +198,17 @@ private DB2ConnectOptions toConnectOptions(DataSourceRuntimeConfig dataSourceRun dataSourceReactiveRuntimeConfig.additionalProperties.forEach(connectOptions::addProperty); + // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with db2. + // and the client_name as tag. + // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and + // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName + connectOptions.setMetricsName("db2|" + dataSourceName); + return connectOptions; } private DB2Pool createPool(Vertx vertx, PoolOptions poolOptions, DB2ConnectOptions dB2ConnectOptions, - String dataSourceName) { + String dataSourceName, Supplier> databases) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { instance = Arc.container().select(DB2PoolCreator.class); @@ -200,7 +220,7 @@ private DB2Pool createPool(Vertx vertx, PoolOptions poolOptions, DB2ConnectOptio DB2PoolCreator.Input input = new DefaultInput(vertx, poolOptions, dB2ConnectOptions); return instance.get().create(input); } - return DB2Pool.pool(vertx, dB2ConnectOptions, poolOptions); + return DB2Pool.pool(vertx, databases, poolOptions); } private static class DefaultInput implements DB2PoolCreator.Input { diff --git a/extensions/reactive-mssql-client/deployment/pom.xml b/extensions/reactive-mssql-client/deployment/pom.xml index a93562d95ffed..4a9278d322a52 100644 --- a/extensions/reactive-mssql-client/deployment/pom.xml +++ b/extensions/reactive-mssql-client/deployment/pom.xml @@ -69,6 +69,12 @@ assertj-core test + + io.quarkus + quarkus-reactive-datasource-deployment + test + test-jar + diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsProvider.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsProvider.java new file mode 100644 index 0000000000000..9f2f5f83428e9 --- /dev/null +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsProvider.java @@ -0,0 +1,13 @@ +package io.quarkus.reactive.mssql.client; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.reactive.datasource.ChangingCredentialsProviderBase; + +@ApplicationScoped +public class ChangingCredentialsProvider extends ChangingCredentialsProviderBase { + + public ChangingCredentialsProvider() { + super("sa", "A_Str0ng_Required_Password", "user2", "user2_Has_A_Str0ng_Required_Password"); + } +} diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsTest.java new file mode 100644 index 0000000000000..9f9720b92a10c --- /dev/null +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsTest.java @@ -0,0 +1,20 @@ +package io.quarkus.reactive.mssql.client; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.reactive.datasource.ChangingCredentialsTestBase; +import io.quarkus.test.QuarkusUnitTest; + +public class ChangingCredentialsTest extends ChangingCredentialsTestBase { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(ChangingCredentialsProvider.class) + .addClass(ChangingCredentialsTestResource.class) + .addAsResource("application-changing-credentials.properties", "application.properties")); + + public ChangingCredentialsTest() { + super("dbo", "user2"); + } +} diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsTestResource.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsTestResource.java new file mode 100644 index 0000000000000..dc832f0855a76 --- /dev/null +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ChangingCredentialsTestResource.java @@ -0,0 +1,41 @@ +package io.quarkus.reactive.mssql.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.enterprise.event.Observes; +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.quarkus.runtime.StartupEvent; +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.mssqlclient.MSSQLPool; + +@Path("/test") +public class ChangingCredentialsTestResource { + + @Inject + MSSQLPool client; + + @Inject + ChangingCredentialsProvider credentialsProvider; + + void addUser(@Observes StartupEvent ignored) { + client.query("CREATE LOGIN user2 WITH PASSWORD = 'user2_Has_A_Str0ng_Required_Password'").executeAndAwait(); + client.query("CREATE USER user2 FOR LOGIN user2").executeAndAwait(); + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Uni connect() { + return client.query("SELECT CURRENT_USER").execute() + .map(rowSet -> { + assertEquals(1, rowSet.size()); + return Response.ok(rowSet.iterator().next().getString(0)).build(); + }).eventually(credentialsProvider::changeProperties); + } + +} diff --git a/extensions/reactive-mssql-client/deployment/src/test/resources/application-changing-credentials.properties b/extensions/reactive-mssql-client/deployment/src/test/resources/application-changing-credentials.properties new file mode 100644 index 0000000000000..5ecf52b7f0e90 --- /dev/null +++ b/extensions/reactive-mssql-client/deployment/src/test/resources/application-changing-credentials.properties @@ -0,0 +1,5 @@ +quarkus.datasource.db-kind=mssql +quarkus.datasource.credentials-provider=changing +quarkus.datasource.reactive.url=${reactive-mssql.url} +quarkus.datasource.reactive.max-size=1 +quarkus.datasource.reactive.idle-timeout=PT1S diff --git a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java index 8a9073a040dd8..e57900425429c 100644 --- a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java +++ b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java @@ -25,16 +25,20 @@ import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig; import io.quarkus.reactive.mssql.client.MSSQLPoolCreator; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.impl.VertxInternal; import io.vertx.mssqlclient.MSSQLConnectOptions; import io.vertx.mssqlclient.MSSQLPool; import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.impl.Utils; @SuppressWarnings("deprecation") @Recorder @@ -50,7 +54,7 @@ public RuntimeValue configureMSSQLPool(RuntimeValue vertx, DataSourcesReactiveMSSQLConfig dataSourcesReactiveMSSQLConfig, ShutdownContext shutdown) { - MSSQLPool mssqlPool = initialize(vertx.getValue(), + MSSQLPool mssqlPool = initialize((VertxInternal) vertx.getValue(), eventLoopCount.get(), dataSourceName, dataSourcesRuntimeConfig.getDataSourceRuntimeConfig(dataSourceName), @@ -65,23 +69,34 @@ public RuntimeValue mutinyMSSQLPool(Runti return new RuntimeValue<>(io.vertx.mutiny.mssqlclient.MSSQLPool.newInstance(mssqlPool.getValue())); } - private MSSQLPool initialize(Vertx vertx, + private MSSQLPool initialize(VertxInternal vertx, Integer eventLoopCount, String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactiveMSSQLConfig dataSourceReactiveMSSQLConfig) { PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveMSSQLConfig); - MSSQLConnectOptions mssqlConnectOptions = toMSSQLConnectOptions(dataSourceRuntimeConfig, + MSSQLConnectOptions mssqlConnectOptions = toMSSQLConnectOptions(dataSourceName, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveMSSQLConfig); + Supplier> databasesSupplier = toDatabasesSupplier(vertx, List.of(mssqlConnectOptions), + dataSourceRuntimeConfig); + return createPool(vertx, poolOptions, mssqlConnectOptions, dataSourceName, databasesSupplier); + } - // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with mssql. - // with the client_name as tag. - // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and - // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName - mssqlConnectOptions.setMetricsName("mssql|" + dataSourceName); - - return createPool(vertx, poolOptions, mssqlConnectOptions, dataSourceName); + private Supplier> toDatabasesSupplier(Vertx vertx, + List mssqlConnectOptionsList, + DataSourceRuntimeConfig dataSourceRuntimeConfig) { + Supplier> supplier; + if (dataSourceRuntimeConfig.credentialsProvider.isPresent()) { + String beanName = dataSourceRuntimeConfig.credentialsProviderName.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); + String name = dataSourceRuntimeConfig.credentialsProvider.get(); + supplier = new ConnectOptionsSupplier<>(vertx, credentialsProvider, name, mssqlConnectOptionsList, + MSSQLConnectOptions::new); + } else { + supplier = Utils.roundRobinSupplier(mssqlConnectOptionsList); + } + return supplier; } private PoolOptions toPoolOptions(Integer eventLoopCount, @@ -114,7 +129,7 @@ private PoolOptions toPoolOptions(Integer eventLoopCount, return poolOptions; } - private MSSQLConnectOptions toMSSQLConnectOptions(DataSourceRuntimeConfig dataSourceRuntimeConfig, + private MSSQLConnectOptions toMSSQLConnectOptions(String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactiveMSSQLConfig dataSourceReactiveMSSQLConfig) { MSSQLConnectOptions mssqlConnectOptions; @@ -185,11 +200,17 @@ private MSSQLConnectOptions toMSSQLConnectOptions(DataSourceRuntimeConfig dataSo dataSourceReactiveRuntimeConfig.additionalProperties.forEach(mssqlConnectOptions::addProperty); + // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with mssql. + // with the client_name as tag. + // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and + // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName + mssqlConnectOptions.setMetricsName("mssql|" + dataSourceName); + return mssqlConnectOptions; } private MSSQLPool createPool(Vertx vertx, PoolOptions poolOptions, MSSQLConnectOptions mSSQLConnectOptions, - String dataSourceName) { + String dataSourceName, Supplier> databases) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { instance = Arc.container().select(MSSQLPoolCreator.class); @@ -201,7 +222,7 @@ private MSSQLPool createPool(Vertx vertx, PoolOptions poolOptions, MSSQLConnectO MSSQLPoolCreator.Input input = new DefaultInput(vertx, poolOptions, mSSQLConnectOptions); return instance.get().create(input); } - return MSSQLPool.pool(vertx, mSSQLConnectOptions, poolOptions); + return MSSQLPool.pool(vertx, databases, poolOptions); } private static class DefaultInput implements MSSQLPoolCreator.Input { diff --git a/extensions/reactive-mysql-client/deployment/pom.xml b/extensions/reactive-mysql-client/deployment/pom.xml index a6f20c96fc9d6..2a2837439668d 100644 --- a/extensions/reactive-mysql-client/deployment/pom.xml +++ b/extensions/reactive-mysql-client/deployment/pom.xml @@ -90,6 +90,12 @@ assertj-core test + + io.quarkus + quarkus-reactive-datasource-deployment + test + test-jar + @@ -210,6 +216,9 @@ ${project.basedir}/custom-mariadbconfig:/etc/mysql/conf.d${volume.access.modifier} + + ${project.basedir}/src/test/resources/setup.sql:/docker-entrypoint-initdb.d/setup.sql + diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsProvider.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsProvider.java new file mode 100644 index 0000000000000..90f9f7579ba8e --- /dev/null +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsProvider.java @@ -0,0 +1,13 @@ +package io.quarkus.reactive.mysql.client; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.reactive.datasource.ChangingCredentialsProviderBase; + +@ApplicationScoped +public class ChangingCredentialsProvider extends ChangingCredentialsProviderBase { + + public ChangingCredentialsProvider() { + super("hibernate_orm_test", "hibernate_orm_test", "user2", "user2"); + } +} diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsTest.java new file mode 100644 index 0000000000000..c49cdffc296aa --- /dev/null +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsTest.java @@ -0,0 +1,20 @@ +package io.quarkus.reactive.mysql.client; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.reactive.datasource.ChangingCredentialsTestBase; +import io.quarkus.test.QuarkusUnitTest; + +public class ChangingCredentialsTest extends ChangingCredentialsTestBase { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(ChangingCredentialsProvider.class) + .addClass(ChangingCredentialsTestResource.class) + .addAsResource("application-changing-credentials.properties", "application.properties")); + + public ChangingCredentialsTest() { + super("hibernate_orm_test@%", "user2@%"); + } +} diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsTestResource.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsTestResource.java new file mode 100644 index 0000000000000..491885db6cac8 --- /dev/null +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ChangingCredentialsTestResource.java @@ -0,0 +1,34 @@ +package io.quarkus.reactive.mysql.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.mysqlclient.MySQLPool; + +@Path("/test") +public class ChangingCredentialsTestResource { + + @Inject + MySQLPool client; + + @Inject + ChangingCredentialsProvider credentialsProvider; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Uni connect() { + return client.query("SELECT CURRENT_USER").execute() + .map(rowSet -> { + assertEquals(1, rowSet.size()); + return Response.ok(rowSet.iterator().next().getString(0)).build(); + }).eventually(credentialsProvider::changeProperties); + } + +} diff --git a/extensions/reactive-mysql-client/deployment/src/test/resources/application-changing-credentials.properties b/extensions/reactive-mysql-client/deployment/src/test/resources/application-changing-credentials.properties new file mode 100644 index 0000000000000..173159df97db6 --- /dev/null +++ b/extensions/reactive-mysql-client/deployment/src/test/resources/application-changing-credentials.properties @@ -0,0 +1,5 @@ +quarkus.datasource.db-kind=mysql +quarkus.datasource.credentials-provider=changing +quarkus.datasource.reactive.url=${reactive-mysql.url} +quarkus.datasource.reactive.max-size=1 +quarkus.datasource.reactive.idle-timeout=PT1S \ No newline at end of file diff --git a/extensions/reactive-mysql-client/deployment/src/test/resources/setup.sql b/extensions/reactive-mysql-client/deployment/src/test/resources/setup.sql new file mode 100644 index 0000000000000..183344aafbc0c --- /dev/null +++ b/extensions/reactive-mysql-client/deployment/src/test/resources/setup.sql @@ -0,0 +1,3 @@ +CREATE USER 'user2'@'%' IDENTIFIED BY 'user2'; +GRANT ALL PRIVILEGES ON hibernate_orm_test.* TO 'user2'@'%'; +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java index 6c3e0ad654a38..5a613d172a18b 100644 --- a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java +++ b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java @@ -26,17 +26,21 @@ import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig; import io.quarkus.reactive.mysql.client.MySQLPoolCreator; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.impl.VertxInternal; import io.vertx.mysqlclient.MySQLConnectOptions; import io.vertx.mysqlclient.MySQLPool; import io.vertx.mysqlclient.SslMode; import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.impl.Utils; @Recorder @SuppressWarnings("deprecation") @@ -52,7 +56,7 @@ public RuntimeValue configureMySQLPool(RuntimeValue vertx, DataSourcesReactiveMySQLConfig dataSourcesReactiveMySQLConfig, ShutdownContext shutdown) { - MySQLPool mysqlPool = initialize(vertx.getValue(), + MySQLPool mysqlPool = initialize((VertxInternal) vertx.getValue(), eventLoopCount.get(), dataSourceName, dataSourcesRuntimeConfig.getDataSourceRuntimeConfig(dataSourceName), @@ -67,24 +71,35 @@ public RuntimeValue mutinyMySQLPool(Runti return new RuntimeValue<>(io.vertx.mutiny.mysqlclient.MySQLPool.newInstance(mysqlPool.getValue())); } - private MySQLPool initialize(Vertx vertx, + private MySQLPool initialize(VertxInternal vertx, Integer eventLoopCount, - String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, + String dataSourceName, + DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactiveMySQLConfig dataSourceReactiveMySQLConfig) { PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveMySQLConfig); - List mysqlConnectOptionsList = toMySQLConnectOptions(dataSourceRuntimeConfig, + List mySQLConnectOptions = toMySQLConnectOptions(dataSourceName, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveMySQLConfig); + Supplier> databasesSupplier = toDatabasesSupplier(vertx, mySQLConnectOptions, + dataSourceRuntimeConfig); + return createPool(vertx, poolOptions, mySQLConnectOptions, dataSourceName, databasesSupplier); + } - // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with mysql. - // and the client_name as tag. - // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and - // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName - mysqlConnectOptionsList.forEach( - mysqlConnectOptions -> mysqlConnectOptions.setMetricsName("mysql|" + dataSourceName)); - - return createPool(vertx, poolOptions, mysqlConnectOptionsList, dataSourceName); + private Supplier> toDatabasesSupplier(Vertx vertx, + List mySQLConnectOptions, + DataSourceRuntimeConfig dataSourceRuntimeConfig) { + Supplier> supplier; + if (dataSourceRuntimeConfig.credentialsProvider.isPresent()) { + String beanName = dataSourceRuntimeConfig.credentialsProviderName.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); + String name = dataSourceRuntimeConfig.credentialsProvider.get(); + supplier = new ConnectOptionsSupplier<>(vertx, credentialsProvider, name, mySQLConnectOptions, + MySQLConnectOptions::new); + } else { + supplier = Utils.roundRobinSupplier(mySQLConnectOptions); + } + return supplier; } private PoolOptions toPoolOptions(Integer eventLoopCount, @@ -122,7 +137,8 @@ private PoolOptions toPoolOptions(Integer eventLoopCount, return poolOptions; } - private List toMySQLConnectOptions(DataSourceRuntimeConfig dataSourceRuntimeConfig, + private List toMySQLConnectOptions(String dataSourceName, + DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactiveMySQLConfig dataSourceReactiveMySQLConfig) { List mysqlConnectOptionsList = new ArrayList<>(); @@ -203,13 +219,19 @@ private List toMySQLConnectOptions(DataSourceRuntimeConfig dataSourceReactiveMySQLConfig.authenticationPlugin.ifPresent(mysqlConnectOptions::setAuthenticationPlugin); dataSourceReactiveRuntimeConfig.additionalProperties.forEach(mysqlConnectOptions::addProperty); + + // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with mysql. + // and the client_name as tag. + // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and + // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName + mysqlConnectOptions.setMetricsName("mysql|" + dataSourceName); }); return mysqlConnectOptionsList; } private MySQLPool createPool(Vertx vertx, PoolOptions poolOptions, List mySQLConnectOptionsList, - String dataSourceName) { + String dataSourceName, Supplier> databases) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { instance = Arc.container().select(MySQLPoolCreator.class); @@ -221,7 +243,7 @@ private MySQLPool createPool(Vertx vertx, PoolOptions poolOptions, Listassertj-core test + + io.quarkus + quarkus-reactive-datasource-deployment + test + test-jar + 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..ca061fda7cfa7 --- /dev/null +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsProvider.java @@ -0,0 +1,13 @@ +package io.quarkus.reactive.oracle.client; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.reactive.datasource.ChangingCredentialsProviderBase; + +@ApplicationScoped +public class ChangingCredentialsProvider extends ChangingCredentialsProviderBase { + + public ChangingCredentialsProvider() { + super("SYSTEM", "hibernate_orm_test", "user2", "user2"); + } +} 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..e88bd42b2e1d1 --- /dev/null +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ChangingCredentialsTest.java @@ -0,0 +1,20 @@ +package io.quarkus.reactive.oracle.client; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.reactive.datasource.ChangingCredentialsTestBase; +import io.quarkus.test.QuarkusUnitTest; + +public class ChangingCredentialsTest extends ChangingCredentialsTestBase { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(ChangingCredentialsProvider.class) + .addClass(ChangingCredentialsTestResource.class) + .addAsResource("application-changing-credentials.properties", "application.properties")); + + public ChangingCredentialsTest() { + super("SYSTEM", "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..ef5c536e0fa87 --- /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.enterprise.event.Observes; +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.quarkus.runtime.StartupEvent; +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.oracleclient.OraclePool; + +@Path("/test") +public class ChangingCredentialsTestResource { + + @Inject + OraclePool client; + + @Inject + ChangingCredentialsProvider credentialsProvider; + + void addUser(@Observes StartupEvent ignored) { + client.query("CREATE USER user2 IDENTIFIED BY user2").executeAndAwait(); + client.query("GRANT CREATE SESSION TO user2").executeAndAwait(); + } + + @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 2c283f6839845..1bd6b3809a6a0 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 @@ -19,16 +19,20 @@ import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig; 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.Future; import io.vertx.core.Vertx; +import io.vertx.core.impl.VertxInternal; import io.vertx.oracleclient.OracleConnectOptions; import io.vertx.oracleclient.OraclePool; import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.impl.Utils; @SuppressWarnings("deprecation") @Recorder @@ -44,7 +48,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), @@ -59,7 +63,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, @@ -67,15 +71,27 @@ private OraclePool initialize(Vertx vertx, DataSourceReactiveOracleConfig dataSourceReactiveOracleConfig) { PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveOracleConfig); - OracleConnectOptions oracleConnectOptions = toOracleConnectOptions(dataSourceRuntimeConfig, + OracleConnectOptions oracleConnectOptions = toOracleConnectOptions(dataSourceName, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveOracleConfig); - // 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); + Supplier> databasesSupplier = toDatabasesSupplier(vertx, List.of(oracleConnectOptions), + dataSourceRuntimeConfig); + return createPool(vertx, poolOptions, oracleConnectOptions, dataSourceName, databasesSupplier); + } - return createPool(vertx, poolOptions, oracleConnectOptions, dataSourceName); + private Supplier> toDatabasesSupplier(Vertx vertx, + List oracleConnectOptions, + DataSourceRuntimeConfig dataSourceRuntimeConfig) { + Supplier> supplier; + if (dataSourceRuntimeConfig.credentialsProvider.isPresent()) { + String beanName = dataSourceRuntimeConfig.credentialsProviderName.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); + String name = dataSourceRuntimeConfig.credentialsProvider.get(); + supplier = new ConnectOptionsSupplier<>(vertx, credentialsProvider, name, oracleConnectOptions, + OracleConnectOptions::new); + } else { + supplier = Utils.roundRobinSupplier(oracleConnectOptions); + } + return supplier; } private PoolOptions toPoolOptions(Integer eventLoopCount, @@ -108,7 +124,7 @@ private PoolOptions toPoolOptions(Integer eventLoopCount, return poolOptions; } - private OracleConnectOptions toOracleConnectOptions(DataSourceRuntimeConfig dataSourceRuntimeConfig, + private OracleConnectOptions toOracleConnectOptions(String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactiveOracleConfig dataSourceReactiveOracleConfig) { OracleConnectOptions oracleConnectOptions; @@ -154,11 +170,17 @@ private OracleConnectOptions toOracleConnectOptions(DataSourceRuntimeConfig data dataSourceReactiveRuntimeConfig.additionalProperties.forEach(oracleConnectOptions::addProperty); + // 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 oracleConnectOptions; } private OraclePool createPool(Vertx vertx, PoolOptions poolOptions, OracleConnectOptions oracleConnectOptions, - String dataSourceName) { + String dataSourceName, Supplier> databases) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { instance = Arc.container().select(OraclePoolCreator.class); @@ -170,7 +192,7 @@ 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); + return OraclePool.pool(vertx, databases, poolOptions); } private static class DefaultInput implements OraclePoolCreator.Input { diff --git a/extensions/reactive-pg-client/deployment/pom.xml b/extensions/reactive-pg-client/deployment/pom.xml index 71fea9a753443..21918b9144407 100644 --- a/extensions/reactive-pg-client/deployment/pom.xml +++ b/extensions/reactive-pg-client/deployment/pom.xml @@ -69,6 +69,12 @@ assertj-core test + + io.quarkus + quarkus-reactive-datasource-deployment + test + test-jar + diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsProvider.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsProvider.java new file mode 100644 index 0000000000000..aff80b36856bf --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsProvider.java @@ -0,0 +1,13 @@ +package io.quarkus.reactive.pg.client; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.quarkus.reactive.datasource.ChangingCredentialsProviderBase; + +@ApplicationScoped +public class ChangingCredentialsProvider extends ChangingCredentialsProviderBase { + + public ChangingCredentialsProvider() { + super("hibernate_orm_test", "hibernate_orm_test", "user2", "user2"); + } +} diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsTest.java new file mode 100644 index 0000000000000..10d145ab5c856 --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsTest.java @@ -0,0 +1,20 @@ +package io.quarkus.reactive.pg.client; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.reactive.datasource.ChangingCredentialsTestBase; +import io.quarkus.test.QuarkusUnitTest; + +public class ChangingCredentialsTest extends ChangingCredentialsTestBase { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(ChangingCredentialsProvider.class) + .addClass(ChangingCredentialsTestResource.class) + .addAsResource("application-changing-credentials.properties", "application.properties")); + + public ChangingCredentialsTest() { + super("hibernate_orm_test", "user2"); + } +} diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsTestResource.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsTestResource.java new file mode 100644 index 0000000000000..bf0462be35877 --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ChangingCredentialsTestResource.java @@ -0,0 +1,40 @@ +package io.quarkus.reactive.pg.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.enterprise.event.Observes; +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.quarkus.runtime.StartupEvent; +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.pgclient.PgPool; + +@Path("/test") +public class ChangingCredentialsTestResource { + + @Inject + PgPool client; + + @Inject + ChangingCredentialsProvider credentialsProvider; + + void addUser(@Observes StartupEvent ignored) { + client.query("CREATE USER user2 WITH PASSWORD 'user2' SUPERUSER").executeAndAwait(); + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Uni connect() { + return client.query("SELECT CURRENT_USER").execute() + .map(pgRowSet -> { + assertEquals(1, pgRowSet.size()); + return Response.ok(pgRowSet.iterator().next().getString(0)).build(); + }).eventually(credentialsProvider::changeProperties); + } + +} diff --git a/extensions/reactive-pg-client/deployment/src/test/resources/application-changing-credentials.properties b/extensions/reactive-pg-client/deployment/src/test/resources/application-changing-credentials.properties new file mode 100644 index 0000000000000..d76f5d77b20c4 --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/test/resources/application-changing-credentials.properties @@ -0,0 +1,5 @@ +quarkus.datasource.db-kind=postgresql +quarkus.datasource.credentials-provider=changing +quarkus.datasource.reactive.url=${reactive-postgres.url} +quarkus.datasource.reactive.max-size=1 +quarkus.datasource.reactive.idle-timeout=PT1S diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java index d7763a6d5f4e2..cd929ccd1a511 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java @@ -26,17 +26,21 @@ import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig; import io.quarkus.reactive.pg.client.PgPoolCreator; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.impl.VertxInternal; import io.vertx.pgclient.PgConnectOptions; import io.vertx.pgclient.PgPool; import io.vertx.pgclient.SslMode; import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.impl.Utils; @Recorder @SuppressWarnings("deprecation") @@ -52,7 +56,7 @@ public RuntimeValue configurePgPool(RuntimeValue vertx, DataSourcesReactivePostgreSQLConfig dataSourcesReactivePostgreSQLConfig, ShutdownContext shutdown) { - PgPool pgPool = initialize(vertx.getValue(), + PgPool pgPool = initialize((VertxInternal) vertx.getValue(), eventLoopCount.get(), dataSourceName, dataSourcesRuntimeConfig.getDataSourceRuntimeConfig(dataSourceName), @@ -67,7 +71,7 @@ public RuntimeValue mutinyPgPool(RuntimeValue

(io.vertx.mutiny.pgclient.PgPool.newInstance(pgPool.getValue())); } - private PgPool initialize(Vertx vertx, + private PgPool initialize(VertxInternal vertx, Integer eventLoopCount, String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, @@ -75,15 +79,26 @@ private PgPool initialize(Vertx vertx, DataSourceReactivePostgreSQLConfig dataSourceReactivePostgreSQLConfig) { PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactivePostgreSQLConfig); - List pgConnectOptionsList = toPgConnectOptions(dataSourceRuntimeConfig, + List pgConnectOptionsList = toPgConnectOptions(dataSourceName, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactivePostgreSQLConfig); - // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with postgresql. - // and the client_name as tag. - // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and - // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName - pgConnectOptionsList.forEach(pgConnectOptions -> pgConnectOptions.setMetricsName("postgresql|" + dataSourceName)); + Supplier> databasesSupplier = toDatabasesSupplier(vertx, pgConnectOptionsList, + dataSourceRuntimeConfig); + return createPool(vertx, poolOptions, pgConnectOptionsList, dataSourceName, databasesSupplier); + } - return createPool(vertx, poolOptions, pgConnectOptionsList, dataSourceName); + private Supplier> toDatabasesSupplier(Vertx vertx, List pgConnectOptionsList, + DataSourceRuntimeConfig dataSourceRuntimeConfig) { + Supplier> supplier; + if (dataSourceRuntimeConfig.credentialsProvider.isPresent()) { + String beanName = dataSourceRuntimeConfig.credentialsProviderName.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(beanName); + String name = dataSourceRuntimeConfig.credentialsProvider.get(); + supplier = new ConnectOptionsSupplier<>(vertx, credentialsProvider, name, pgConnectOptionsList, + PgConnectOptions::new); + } else { + supplier = Utils.roundRobinSupplier(pgConnectOptionsList); + } + return supplier; } private PoolOptions toPoolOptions(Integer eventLoopCount, @@ -116,7 +131,7 @@ private PoolOptions toPoolOptions(Integer eventLoopCount, return poolOptions; } - private List toPgConnectOptions(DataSourceRuntimeConfig dataSourceRuntimeConfig, + private List toPgConnectOptions(String dataSourceName, DataSourceRuntimeConfig dataSourceRuntimeConfig, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactivePostgreSQLConfig dataSourceReactivePostgreSQLConfig) { List pgConnectOptionsList = new ArrayList<>(); @@ -166,8 +181,9 @@ private List toPgConnectOptions(DataSourceRuntimeConfig dataSo pgConnectOptions.setSslMode(sslMode); // If sslMode is verify-full, we also need a hostname verification algorithm - if (sslMode == SslMode.VERIFY_FULL && (!dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm - .isPresent() || "".equals(dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm.get()))) { + if (sslMode == SslMode.VERIFY_FULL + && (!dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm.isPresent() + || "".equals(dataSourceReactiveRuntimeConfig.hostnameVerificationAlgorithm.get()))) { throw new IllegalArgumentException( "quarkus.datasource.reactive.hostname-verification-algorithm must be specified under verify-full sslmode"); } @@ -191,13 +207,20 @@ private List toPgConnectOptions(DataSourceRuntimeConfig dataSo pgConnectOptions::setHostnameVerificationAlgorithm); dataSourceReactiveRuntimeConfig.additionalProperties.forEach(pgConnectOptions::addProperty); + + // Use the convention defined by Quarkus Micrometer Vert.x metrics to create metrics prefixed with postgresql. + // and the client_name as tag. + // See io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractPrefix and + // io.quarkus.micrometer.runtime.binder.vertx.VertxMeterBinderAdapter.extractClientName + pgConnectOptions.setMetricsName("postgresql|" + dataSourceName); + }); return pgConnectOptionsList; } private PgPool createPool(Vertx vertx, PoolOptions poolOptions, List pgConnectOptionsList, - String dataSourceName) { + String dataSourceName, Supplier> databases) { Instance instance; if (DataSourceUtil.isDefault(dataSourceName)) { instance = Arc.container().select(PgPoolCreator.class); @@ -209,7 +232,7 @@ private PgPool createPool(Vertx vertx, PoolOptions poolOptions, List