Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add maxLifetime to reactive datasource configurations #34985

Merged
merged 2 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/src/main/asciidoc/credentials-provider.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,40 @@
property from configuration. All consuming extensions do support the ability to fetch both the username
and password from the provider, or just the password.

== Time Limited Credentials

Check warning on line 86 in docs/src/main/asciidoc/credentials-provider.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Time Limited Credentials'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Time Limited Credentials'.", "location": {"path": "docs/src/main/asciidoc/credentials-provider.adoc", "range": {"start": {"line": 86, "column": 4}}}, "severity": "INFO"}

A Credentials Provider may provide time limited credentials. For instance, the `vault` extension. When using

Check warning on line 88 in docs/src/main/asciidoc/credentials-provider.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses it. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses it.", "location": {"path": "docs/src/main/asciidoc/credentials-provider.adoc", "range": {"start": {"line": 88, "column": 24}}}, "severity": "WARNING"}
time limited credentials, it is important to understand that consuming extensions will not have their
credentials refreshed automatically by the Credentials Provider. Each extension must be configured to recycle its
connections before the credentials expire.

=== Datasources

Check warning on line 93 in docs/src/main/asciidoc/credentials-provider.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Datasources'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Datasources'?", "location": {"path": "docs/src/main/asciidoc/credentials-provider.adoc", "range": {"start": {"line": 93, "column": 5}}}, "severity": "WARNING"}

Datastore connections are typically pooled. When using a time limited credentials provider, the pool must be

Check warning on line 95 in docs/src/main/asciidoc/credentials-provider.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Datastore'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Datastore'?", "location": {"path": "docs/src/main/asciidoc/credentials-provider.adoc", "range": {"start": {"line": 95, "column": 1}}}, "severity": "WARNING"}
configured to recycle connections before each connection's credentials expire. Both JDBC and Reactive datasources

Check warning on line 96 in docs/src/main/asciidoc/credentials-provider.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'datasources'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'datasources'?", "location": {"path": "docs/src/main/asciidoc/credentials-provider.adoc", "range": {"start": {"line": 96, "column": 103}}}, "severity": "WARNING"}
have a `max-lifetime` configuration property that can be used to achieve this.

.JDBC Datasource
[source, properties]
----
quarkus.datasource.jdbc.max-lifetime=60m
----

.Reactive Datasource
[source, properties]
----
quarkus.datasource.reactive.max-lifetime=60m
----

NOTE: It is the developer's responsibility to ensure that the configuration of the datasource's `max-lifetime`
property is less than the credentials expiration time.

=== RabbitMQ

When using the `smallrye-reactive-messaging-rabbitmq` extension there is no configuration needed. The
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what's different with the RabbitMQ extension? If the CredentialsProvider has the "expires-at property, couldn't we use the value to configure the pool correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RabbitMQ connections supports this automatically, it has built support for dynamic credentials.

Vert.x connection pooling does not support this. I made the PR to add max-lifetime to the Vert.x pool and in its current state I believe it would be hard to support this type of feature. Currently the SqlConnectionPool doesn't allow configuration of per-connection properties. If we gain the ability to set properties (specifically something like max lifetime) per connection this could be implemented

extension will automatically recycle connections before their credentials expire based on the expiration
timestamp provided by the Credentials Provider.

== Custom Credentials Provider

Implementing a custom credentials provider is the only option when a vault product is not yet supported in Quarkus, or if credentials need to be retrieved from a custom store.
Expand Down
20 changes: 18 additions & 2 deletions docs/src/main/asciidoc/reactive-sql-clients.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -708,9 +708,9 @@
quarkus.datasource.reactive.url[2]=postgresql://host3:5432/default
----

== Pooled Connection `idle-timeout`
== Pooled connection `idle-timeout`

Check warning on line 711 in docs/src/main/asciidoc/reactive-sql-clients.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'datasources'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'datasources'?", "location": {"path": "docs/src/main/asciidoc/reactive-sql-clients.adoc", "range": {"start": {"line": 711, "column": 25}}}, "severity": "WARNING"}

Reactive datasources can be configured with an `idle-timeout` (in milliseconds).
Reactive datasources can be configured with an `idle-timeout`.
It is the maximum time a connection remains unused in the pool before it is closed.

NOTE: The `idle-timeout` is disabled by default.
Expand All @@ -719,12 +719,28 @@

[source,properties]
----
quarkus.datasource.reactive.idle-timeout=PT60M

Check warning on line 722 in docs/src/main/asciidoc/reactive-sql-clients.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Pooled Connection'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Pooled Connection'.", "location": {"path": "docs/src/main/asciidoc/reactive-sql-clients.adoc", "range": {"start": {"line": 722, "column": 35}}}, "severity": "INFO"}
----

== Pooled Connection `max-lifetime`

In addition to `idle-timeout`, reactive datasources can also be configured with a `max-lifetime`.

Check warning on line 727 in docs/src/main/asciidoc/reactive-sql-clients.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'datasources'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'datasources'?", "location": {"path": "docs/src/main/asciidoc/reactive-sql-clients.adoc", "range": {"start": {"line": 727, "column": 19}}}, "severity": "WARNING"}
It is the maximum time a connection remains in the pool before it is closed and replaced as needed.
The `max-lifetime` allows ensuring the pool has fresh connections with up-to-date configuration.

NOTE: The `max-lifetime` is disabled by default but is an important configuration when using a credentials
provider that provides time limited credentials, like the link:credentials-provider.adoc[Vault credentials provider].

For example, you could ensure connections are recycled after 60 minutes:

[source,properties]
----
quarkus.datasource.reactive.max-lifetime=PT60M
----

== Customizing pool creation

Sometimes, the database connection pool cannot be configured only by declaration.

Check warning on line 743 in docs/src/main/asciidoc/reactive-sql-clients.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses it. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses it.", "location": {"path": "docs/src/main/asciidoc/reactive-sql-clients.adoc", "range": {"start": {"line": 743, "column": 66}}}, "severity": "WARNING"}

Check warning on line 743 in docs/src/main/asciidoc/reactive-sql-clients.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/reactive-sql-clients.adoc", "range": {"start": {"line": 743, "column": 70}}}, "severity": "INFO"}

You may need to read a specific file only present in production, or retrieve configuration data from a proprietary configuration server.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ public interface DataSourceReactiveRuntimeConfig {
@ConfigDocDefault("no timeout")
Optional<Duration> idleTimeout();

/**
* The maximum time a connection remains in the pool, after which it will be closed
* upon return and replaced as necessary.
*/
@ConfigDocDefault("no timeout")
Optional<Duration> maxLifetime();

/**
* Set to true to share the pool among datasources.
* There can be multiple shared pools distinguished by <name>name</name>, when no specific name is set,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.reactive.datasource.runtime;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

public class UnitisedTime {

public final int value;
public final TimeUnit unit;

public UnitisedTime(int value, TimeUnit unit) {
this.value = value;
this.unit = unit;
}

/**
* Convert a {@link Duration} to a {@link UnitisedTime} with the smallest possible
* {@link TimeUnit} starting from {@link TimeUnit#MILLISECONDS}.
*
* @param duration Duration to convert
*
* @return UnitisedTime
*/
public static UnitisedTime unitised(Duration duration) {
if (duration.isNegative()) {
throw new IllegalArgumentException("Duration cannot be negative.");
}

long millis = duration.toMillis();
if (millis < Integer.MAX_VALUE) {
return new UnitisedTime((int) millis, TimeUnit.MILLISECONDS);
}

long seconds = duration.getSeconds();
if (seconds < Integer.MAX_VALUE) {
return new UnitisedTime((int) seconds, TimeUnit.SECONDS);
}

long minutes = duration.toMinutes();
if (minutes < Integer.MAX_VALUE) {
return new UnitisedTime((int) minutes, TimeUnit.MINUTES);
}

long hours = duration.toHours();
if (hours < Integer.MAX_VALUE) {
return new UnitisedTime((int) hours, TimeUnit.HOURS);
}

long days = duration.toDays();
return new UnitisedTime((int) days, TimeUnit.DAYS);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.quarkus.credentials.CredentialsProvider.PASSWORD_PROPERTY_NAME;
import static io.quarkus.credentials.CredentialsProvider.USER_PROPERTY_NAME;
import static io.quarkus.reactive.datasource.runtime.UnitisedTime.unitised;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksKeyCertOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksTrustOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configurePemKeyCertOptions;
Expand All @@ -11,7 +12,6 @@

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import jakarta.enterprise.inject.Instance;
Expand Down Expand Up @@ -108,8 +108,13 @@ private PoolOptions toPoolOptions(Integer eventLoopCount,
poolOptions.setMaxSize(dataSourceReactiveRuntimeConfig.maxSize());

if (dataSourceReactiveRuntimeConfig.idleTimeout().isPresent()) {
int idleTimeout = Math.toIntExact(dataSourceReactiveRuntimeConfig.idleTimeout().get().toMillis());
poolOptions.setIdleTimeout(idleTimeout).setIdleTimeoutUnit(TimeUnit.MILLISECONDS);
var idleTimeout = unitised(dataSourceReactiveRuntimeConfig.idleTimeout().get());
poolOptions.setIdleTimeout(idleTimeout.value).setIdleTimeoutUnit(idleTimeout.unit);
}

if (dataSourceReactiveRuntimeConfig.maxLifetime().isPresent()) {
var maxLifetime = unitised(dataSourceReactiveRuntimeConfig.maxLifetime().get());
poolOptions.setMaxLifetime(maxLifetime.value).setMaxLifetimeUnit(maxLifetime.unit);
}

if (dataSourceReactiveRuntimeConfig.shared()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ quarkus.datasource.credentials-provider=changing
quarkus.datasource.reactive.url=${reactive-mssql.url}
quarkus.datasource.reactive.max-size=1
quarkus.datasource.reactive.idle-timeout=PT1S
quarkus.datasource.reactive.max-lifetime=PT2s
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.quarkus.credentials.CredentialsProvider.PASSWORD_PROPERTY_NAME;
import static io.quarkus.credentials.CredentialsProvider.USER_PROPERTY_NAME;
import static io.quarkus.reactive.datasource.runtime.UnitisedTime.unitised;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksKeyCertOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksTrustOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configurePemKeyCertOptions;
Expand All @@ -11,7 +12,6 @@

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import jakarta.enterprise.inject.Instance;
Expand Down Expand Up @@ -108,8 +108,13 @@ private PoolOptions toPoolOptions(Integer eventLoopCount,
poolOptions.setMaxSize(dataSourceReactiveRuntimeConfig.maxSize());

if (dataSourceReactiveRuntimeConfig.idleTimeout().isPresent()) {
int idleTimeout = Math.toIntExact(dataSourceReactiveRuntimeConfig.idleTimeout().get().toMillis());
poolOptions.setIdleTimeout(idleTimeout).setIdleTimeoutUnit(TimeUnit.MILLISECONDS);
var idleTimeout = unitised(dataSourceReactiveRuntimeConfig.idleTimeout().get());
poolOptions.setIdleTimeout(idleTimeout.value).setIdleTimeoutUnit(idleTimeout.unit);
}

if (dataSourceReactiveRuntimeConfig.maxLifetime().isPresent()) {
var maxLifetime = unitised(dataSourceReactiveRuntimeConfig.maxLifetime().get());
poolOptions.setMaxLifetime(maxLifetime.value).setMaxLifetimeUnit(maxLifetime.unit);
}

if (dataSourceReactiveRuntimeConfig.shared()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,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
quarkus.datasource.reactive.idle-timeout=PT1S
quarkus.datasource.reactive.max-lifetime=PT2s
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.quarkus.credentials.CredentialsProvider.PASSWORD_PROPERTY_NAME;
import static io.quarkus.credentials.CredentialsProvider.USER_PROPERTY_NAME;
import static io.quarkus.reactive.datasource.runtime.UnitisedTime.unitised;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksKeyCertOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksTrustOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configurePemKeyCertOptions;
Expand Down Expand Up @@ -107,8 +108,13 @@ private PoolOptions toPoolOptions(Integer eventLoopCount,
poolOptions.setMaxSize(dataSourceReactiveRuntimeConfig.maxSize());

if (dataSourceReactiveRuntimeConfig.idleTimeout().isPresent()) {
int idleTimeout = Math.toIntExact(dataSourceReactiveRuntimeConfig.idleTimeout().get().toMillis());
poolOptions.setIdleTimeout(idleTimeout).setIdleTimeoutUnit(TimeUnit.MILLISECONDS);
var idleTimeout = unitised(dataSourceReactiveRuntimeConfig.idleTimeout().get());
poolOptions.setIdleTimeout(idleTimeout.value).setIdleTimeoutUnit(idleTimeout.unit);
}

if (dataSourceReactiveRuntimeConfig.maxLifetime().isPresent()) {
var maxLifetime = unitised(dataSourceReactiveRuntimeConfig.maxLifetime().get());
poolOptions.setMaxLifetime(maxLifetime.value).setMaxLifetimeUnit(maxLifetime.unit);
}

if (dataSourceReactiveRuntimeConfig.shared()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ quarkus.datasource.credentials-provider=changing
quarkus.datasource.reactive.url=${reactive-oracledb.url}
quarkus.datasource.reactive.max-size=1
quarkus.datasource.reactive.idle-timeout=PT1S
quarkus.datasource.reactive.max-lifetime=PT2s
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import static io.quarkus.credentials.CredentialsProvider.PASSWORD_PROPERTY_NAME;
import static io.quarkus.credentials.CredentialsProvider.USER_PROPERTY_NAME;
import static io.quarkus.reactive.datasource.runtime.UnitisedTime.unitised;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import jakarta.enterprise.inject.Instance;
Expand Down Expand Up @@ -104,8 +104,13 @@ private PoolOptions toPoolOptions(Integer eventLoopCount,
poolOptions.setMaxSize(dataSourceReactiveRuntimeConfig.maxSize());

if (dataSourceReactiveRuntimeConfig.idleTimeout().isPresent()) {
int idleTimeout = Math.toIntExact(dataSourceReactiveRuntimeConfig.idleTimeout().get().toMillis());
poolOptions.setIdleTimeout(idleTimeout).setIdleTimeoutUnit(TimeUnit.MILLISECONDS);
var idleTimeout = unitised(dataSourceReactiveRuntimeConfig.idleTimeout().get());
poolOptions.setIdleTimeout(idleTimeout.value).setIdleTimeoutUnit(idleTimeout.unit);
}

if (dataSourceReactiveRuntimeConfig.maxLifetime().isPresent()) {
var maxLifetime = unitised(dataSourceReactiveRuntimeConfig.maxLifetime().get());
poolOptions.setMaxLifetime(maxLifetime.value).setMaxLifetimeUnit(maxLifetime.unit);
}

if (dataSourceReactiveRuntimeConfig.shared()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ quarkus.datasource.credentials-provider=changing
quarkus.datasource.reactive.url=${reactive-postgres.url}
quarkus.datasource.reactive.max-size=1
quarkus.datasource.reactive.idle-timeout=PT1S
quarkus.datasource.reactive.max-lifetime=PT2s
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.quarkus.credentials.CredentialsProvider.PASSWORD_PROPERTY_NAME;
import static io.quarkus.credentials.CredentialsProvider.USER_PROPERTY_NAME;
import static io.quarkus.reactive.datasource.runtime.UnitisedTime.unitised;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksKeyCertOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configureJksTrustOptions;
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configurePemKeyCertOptions;
Expand All @@ -12,7 +13,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import jakarta.enterprise.inject.Instance;
Expand Down Expand Up @@ -106,8 +106,13 @@ private PoolOptions toPoolOptions(Integer eventLoopCount,
poolOptions.setMaxSize(dataSourceReactiveRuntimeConfig.maxSize());

if (dataSourceReactiveRuntimeConfig.idleTimeout().isPresent()) {
int idleTimeout = Math.toIntExact(dataSourceReactiveRuntimeConfig.idleTimeout().get().toMillis());
poolOptions.setIdleTimeout(idleTimeout).setIdleTimeoutUnit(TimeUnit.MILLISECONDS);
var idleTimeout = unitised(dataSourceReactiveRuntimeConfig.idleTimeout().get());
poolOptions.setIdleTimeout(idleTimeout.value).setIdleTimeoutUnit(idleTimeout.unit);
}

if (dataSourceReactiveRuntimeConfig.maxLifetime().isPresent()) {
var maxLifetime = unitised(dataSourceReactiveRuntimeConfig.maxLifetime().get());
poolOptions.setMaxLifetime(maxLifetime.value).setMaxLifetimeUnit(maxLifetime.unit);
}

if (dataSourceReactiveRuntimeConfig.shared()) {
Expand Down
Loading