From e585673845902f0428fbda999811a018416d92f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Marti=C5=A1ka?= Date: Mon, 25 Oct 2021 14:05:03 +0200 Subject: [PATCH] Don't include non-configured data sources in health checks --- ...ourceHealthCheckWithExtraProducerTest.java | 106 ++++++++++++++++++ .../runtime/health/DataSourceHealthCheck.java | 38 +++---- ...rcesExcludedFromHealthChecksProcessor.java | 10 +- .../DataSourcesExcludedFromHealthChecks.java | 16 --- ...urcesExcludedFromHealthChecksRecorder.java | 30 ----- .../runtime/DataSourcesHealthSupport.java | 26 +++++ .../DataSourcesHealthSupportRecorder.java | 36 ++++++ .../ReactiveDB2DataSourcesHealthCheck.java | 4 +- .../ReactiveMSSQLDataSourcesHealthCheck.java | 4 +- .../ReactiveMySQLDataSourcesHealthCheck.java | 4 +- .../ReactivePgDataSourcesHealthCheck.java | 4 +- 11 files changed, 197 insertions(+), 81 deletions(-) create mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DataSourceHealthCheckWithExtraProducerTest.java delete mode 100644 extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecks.java delete mode 100644 extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecksRecorder.java create mode 100644 extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupport.java create mode 100644 extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupportRecorder.java diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DataSourceHealthCheckWithExtraProducerTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DataSourceHealthCheckWithExtraProducerTest.java new file mode 100644 index 0000000000000..b58990e052d16 --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DataSourceHealthCheckWithExtraProducerTest.java @@ -0,0 +1,106 @@ +package io.quarkus.agroal.test; + +import static java.lang.annotation.ElementType.FIELD; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +import javax.enterprise.inject.Produces; +import javax.inject.Qualifier; +import javax.sql.DataSource; + +import org.hamcrest.CoreMatchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +/** + * Check that if an application code contains an extra CDI bean that implements javax.sql.DataSource, + * but it is not a data source configured through regular configuration means, then it is not included in + * health checks. + */ +public class DataSourceHealthCheckWithExtraProducerTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ExtraDataSourceProducer.class, ExtraDataSource.class)) + .withConfigurationResource("application-datasources-with-health.properties"); + + static class ExtraDataSourceProducer { + + @Produces + @ExtraDataSource + DataSource extraDs = new DataSource() { + + @Override + public Connection getConnection() throws SQLException { + throw new IllegalStateException(); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + throw new IllegalStateException(); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + } + + @Override + public int getLoginTimeout() throws SQLException { + return 0; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } + }; + + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + @Target({ FIELD }) + static @interface ExtraDataSource { + + } + + @Test + public void testDataSourceHealthCheckExclusion() { + RestAssured.when().get("/q/health/ready") + .then() + .body("status", CoreMatchers.equalTo("UP")); + } + +} diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java index 1f753f95a0e42..2e587224398ee 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java @@ -1,6 +1,5 @@ package io.quarkus.agroal.runtime.health; -import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; @@ -8,7 +7,6 @@ import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.spi.Bean; import javax.sql.DataSource; import org.eclipse.microprofile.health.HealthCheck; @@ -16,34 +14,29 @@ import org.eclipse.microprofile.health.HealthCheckResponseBuilder; import org.eclipse.microprofile.health.Readiness; +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource.DataSourceLiteral; import io.quarkus.arc.Arc; import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.datasource.runtime.DataSourcesExcludedFromHealthChecks; +import io.quarkus.datasource.runtime.DataSourcesHealthSupport; @Readiness @ApplicationScoped public class DataSourceHealthCheck implements HealthCheck { - private static final String DEFAULT_DS = "__default__"; private final Map dataSources = new HashMap<>(); @PostConstruct protected void init() { - Set> beans = Arc.container().beanManager().getBeans(DataSource.class); - DataSourcesExcludedFromHealthChecks excluded = Arc.container().instance(DataSourcesExcludedFromHealthChecks.class) + DataSourcesHealthSupport support = Arc.container().instance(DataSourcesHealthSupport.class) .get(); - Set excludedNames = excluded.getExcludedNames(); - for (Bean bean : beans) { - if (bean.getName() == null) { - if (!excludedNames.contains(DataSourceUtil.DEFAULT_DATASOURCE_NAME)) { - // this is the default DataSource: retrieve it by type - DataSource defaultDs = Arc.container().instance(DataSource.class).get(); - dataSources.put(DEFAULT_DS, defaultDs); - } - } else { - if (!excludedNames.contains(bean.getName())) { - DataSource ds = (DataSource) Arc.container().instance(bean.getName()).get(); - dataSources.put(bean.getName(), ds); - } + Set names = support.getConfiguredNames(); + Set excludedNames = support.getExcludedNames(); + for (String name : names) { + DataSource ds = DataSourceUtil.isDefault(name) + ? (DataSource) Arc.container().instance(DataSource.class).get() + : (DataSource) Arc.container().instance(DataSource.class, new DataSourceLiteral(name)).get(); + if (!excludedNames.contains(name) && ds != null) { + dataSources.put(name, ds); } } } @@ -52,9 +45,10 @@ protected void init() { public HealthCheckResponse call() { HealthCheckResponseBuilder builder = HealthCheckResponse.named("Database connections health check").up(); for (Map.Entry dataSource : dataSources.entrySet()) { - boolean isDefault = DEFAULT_DS.equals(dataSource.getKey()); - try (Connection con = dataSource.getValue().getConnection()) { - boolean valid = con.isValid(0); + boolean isDefault = DataSourceUtil.isDefault(dataSource.getKey()); + AgroalDataSource ads = (AgroalDataSource) dataSource.getValue(); + try { + boolean valid = ads.isHealthy(false); if (!valid) { String data = isDefault ? "validation check failed for the default DataSource" : "validation check failed for DataSource '" + dataSource.getKey() + "'"; diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DataSourcesExcludedFromHealthChecksProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DataSourcesExcludedFromHealthChecksProcessor.java index 47c389cbeeb8f..4a2bc100a952a 100644 --- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DataSourcesExcludedFromHealthChecksProcessor.java +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DataSourcesExcludedFromHealthChecksProcessor.java @@ -6,8 +6,8 @@ import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; -import io.quarkus.datasource.runtime.DataSourcesExcludedFromHealthChecks; -import io.quarkus.datasource.runtime.DataSourcesExcludedFromHealthChecksRecorder; +import io.quarkus.datasource.runtime.DataSourcesHealthSupport; +import io.quarkus.datasource.runtime.DataSourcesHealthSupportRecorder; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; @@ -20,14 +20,14 @@ public class DataSourcesExcludedFromHealthChecksProcessor { @Record(STATIC_INIT) void produceBean( Capabilities capabilities, - DataSourcesExcludedFromHealthChecksRecorder recorder, + DataSourcesHealthSupportRecorder recorder, DataSourcesBuildTimeConfig config, BuildProducer syntheticBeans) { if (capabilities.isPresent(Capability.SMALLRYE_HEALTH)) { - syntheticBeans.produce(SyntheticBeanBuildItem.configure(DataSourcesExcludedFromHealthChecks.class) + syntheticBeans.produce(SyntheticBeanBuildItem.configure(DataSourcesHealthSupport.class) .scope(Singleton.class) .unremovable() - .runtimeValue(recorder.configureDataSourcesExcludedFromHealthChecks(config)) + .runtimeValue(recorder.configureDataSourcesHealthSupport(config)) .done()); } } diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecks.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecks.java deleted file mode 100644 index 1069bf4bed79f..0000000000000 --- a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecks.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.quarkus.datasource.runtime; - -import java.util.Set; - -public class DataSourcesExcludedFromHealthChecks { - - private final Set excludedNames; - - public DataSourcesExcludedFromHealthChecks(Set excludedNames) { - this.excludedNames = excludedNames; - } - - public Set getExcludedNames() { - return excludedNames; - } -} diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecksRecorder.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecksRecorder.java deleted file mode 100644 index 2c6eeb27622d9..0000000000000 --- a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesExcludedFromHealthChecksRecorder.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.quarkus.datasource.runtime; - -import static java.util.stream.Collectors.toUnmodifiableSet; - -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.runtime.RuntimeValue; -import io.quarkus.runtime.annotations.Recorder; - -@Recorder -public class DataSourcesExcludedFromHealthChecksRecorder { - - public RuntimeValue configureDataSourcesExcludedFromHealthChecks( - DataSourcesBuildTimeConfig config) { - Stream.Builder builder = Stream.builder(); - if (config.defaultDataSource.healthExclude) { - builder.add(DataSourceUtil.DEFAULT_DATASOURCE_NAME); - } - for (Map.Entry dataSource : config.namedDataSources.entrySet()) { - if (dataSource.getValue().healthExclude) { - builder.add(dataSource.getKey()); - } - } - Set excludedNames = builder.build().collect(toUnmodifiableSet()); - return new RuntimeValue<>(new DataSourcesExcludedFromHealthChecks(excludedNames)); - } -} diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupport.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupport.java new file mode 100644 index 0000000000000..00a3c19a91b90 --- /dev/null +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupport.java @@ -0,0 +1,26 @@ +package io.quarkus.datasource.runtime; + +import java.util.Set; + +/** + * Helper class that holds the names of all configured data sources, along with the names of those + * that are excluded from health checks. This is used by health check implementation classes. + */ +public class DataSourcesHealthSupport { + + private final Set configuredNames; + private final Set excludedNames; + + public DataSourcesHealthSupport(Set configuredNames, Set excludedNames) { + this.configuredNames = configuredNames; + this.excludedNames = excludedNames; + } + + public Set getConfiguredNames() { + return configuredNames; + } + + public Set getExcludedNames() { + return excludedNames; + } +} diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupportRecorder.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupportRecorder.java new file mode 100644 index 0000000000000..1a59409ecdd9c --- /dev/null +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourcesHealthSupportRecorder.java @@ -0,0 +1,36 @@ +package io.quarkus.datasource.runtime; + +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class DataSourcesHealthSupportRecorder { + + public RuntimeValue configureDataSourcesHealthSupport( + DataSourcesBuildTimeConfig config) { + Stream.Builder configured = Stream.builder(); + Stream.Builder excluded = Stream.builder(); + if (config.defaultDataSource.dbKind.isPresent()) { + configured.add(DataSourceUtil.DEFAULT_DATASOURCE_NAME); + } + if (config.defaultDataSource.healthExclude) { + excluded.add(DataSourceUtil.DEFAULT_DATASOURCE_NAME); + } + for (Map.Entry dataSource : config.namedDataSources.entrySet()) { + configured.add(dataSource.getKey()); + if (dataSource.getValue().healthExclude) { + excluded.add(dataSource.getKey()); + } + } + Set names = configured.build().collect(toUnmodifiableSet()); + Set excludedNames = excluded.build().collect(toUnmodifiableSet()); + return new RuntimeValue<>(new DataSourcesHealthSupport(names, excludedNames)); + } +} diff --git a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java index 78548f24d0ede..214f018c200ca 100644 --- a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java +++ b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java @@ -20,7 +20,7 @@ import io.quarkus.arc.ArcContainer; import io.quarkus.arc.InstanceHandle; import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.datasource.runtime.DataSourcesExcludedFromHealthChecks; +import io.quarkus.datasource.runtime.DataSourcesHealthSupport; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.vertx.mutiny.db2client.DB2Pool; @@ -37,7 +37,7 @@ class ReactiveDB2DataSourcesHealthCheck implements HealthCheck { @PostConstruct protected void init() { ArcContainer container = Arc.container(); - DataSourcesExcludedFromHealthChecks excluded = container.instance(DataSourcesExcludedFromHealthChecks.class).get(); + DataSourcesHealthSupport excluded = container.instance(DataSourcesHealthSupport.class).get(); Set excludedNames = excluded.getExcludedNames(); for (InstanceHandle handle : container.select(DB2Pool.class, Any.Literal.INSTANCE).handles()) { String db2PoolName = getDB2PoolName(handle.getBean()); diff --git a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/health/ReactiveMSSQLDataSourcesHealthCheck.java b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/health/ReactiveMSSQLDataSourcesHealthCheck.java index da79a9a2e7216..e1c981075a1d0 100644 --- a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/health/ReactiveMSSQLDataSourcesHealthCheck.java +++ b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/health/ReactiveMSSQLDataSourcesHealthCheck.java @@ -11,7 +11,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; import io.quarkus.arc.InstanceHandle; -import io.quarkus.datasource.runtime.DataSourcesExcludedFromHealthChecks; +import io.quarkus.datasource.runtime.DataSourcesHealthSupport; import io.quarkus.reactive.datasource.runtime.ReactiveDatasourceHealthCheck; import io.vertx.mssqlclient.MSSQLPool; @@ -26,7 +26,7 @@ public ReactiveMSSQLDataSourcesHealthCheck() { @PostConstruct protected void init() { ArcContainer container = Arc.container(); - DataSourcesExcludedFromHealthChecks excluded = container.instance(DataSourcesExcludedFromHealthChecks.class).get(); + DataSourcesHealthSupport excluded = container.instance(DataSourcesHealthSupport.class).get(); Set excludedNames = excluded.getExcludedNames(); for (InstanceHandle handle : container.select(MSSQLPool.class, Any.Literal.INSTANCE).handles()) { String poolName = getPoolName(handle.getBean()); diff --git a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java index c9420f2a0ddb1..b7875f7e25cfc 100644 --- a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java +++ b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java @@ -11,7 +11,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; import io.quarkus.arc.InstanceHandle; -import io.quarkus.datasource.runtime.DataSourcesExcludedFromHealthChecks; +import io.quarkus.datasource.runtime.DataSourcesHealthSupport; import io.quarkus.reactive.datasource.runtime.ReactiveDatasourceHealthCheck; import io.vertx.mysqlclient.MySQLPool; @@ -26,7 +26,7 @@ public ReactiveMySQLDataSourcesHealthCheck() { @PostConstruct protected void init() { ArcContainer container = Arc.container(); - DataSourcesExcludedFromHealthChecks excluded = container.instance(DataSourcesExcludedFromHealthChecks.class).get(); + DataSourcesHealthSupport excluded = container.instance(DataSourcesHealthSupport.class).get(); Set excludedNames = excluded.getExcludedNames(); for (InstanceHandle handle : container.select(MySQLPool.class, Any.Literal.INSTANCE).handles()) { String poolName = getPoolName(handle.getBean()); diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java index c84d420677022..0fd0aacfa022e 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java @@ -11,7 +11,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; import io.quarkus.arc.InstanceHandle; -import io.quarkus.datasource.runtime.DataSourcesExcludedFromHealthChecks; +import io.quarkus.datasource.runtime.DataSourcesHealthSupport; import io.quarkus.reactive.datasource.runtime.ReactiveDatasourceHealthCheck; import io.vertx.pgclient.PgPool; @@ -26,7 +26,7 @@ public ReactivePgDataSourcesHealthCheck() { @PostConstruct protected void init() { ArcContainer container = Arc.container(); - DataSourcesExcludedFromHealthChecks excluded = container.instance(DataSourcesExcludedFromHealthChecks.class).get(); + DataSourcesHealthSupport excluded = container.instance(DataSourcesHealthSupport.class).get(); Set excludedNames = excluded.getExcludedNames(); for (InstanceHandle handle : container.select(PgPool.class, Any.Literal.INSTANCE).handles()) { String poolName = getPoolName(handle.getBean());