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

Don't include non-configured data sources in health checks #21003

Merged
merged 1 commit into from
Nov 2, 2021
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
Original file line number Diff line number Diff line change
@@ -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> T unwrap(Class<T> 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"));
}

}
Original file line number Diff line number Diff line change
@@ -1,49 +1,42 @@
package io.quarkus.agroal.runtime.health;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

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;
import org.eclipse.microprofile.health.HealthCheckResponse;
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<String, DataSource> dataSources = new HashMap<>();

@PostConstruct
protected void init() {
Set<Bean<?>> beans = Arc.container().beanManager().getBeans(DataSource.class);
DataSourcesExcludedFromHealthChecks excluded = Arc.container().instance(DataSourcesExcludedFromHealthChecks.class)
DataSourcesHealthSupport support = Arc.container().instance(DataSourcesHealthSupport.class)
.get();
Set<String> 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<String> names = support.getConfiguredNames();
Set<String> 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);
}
}
}
Expand All @@ -52,9 +45,10 @@ protected void init() {
public HealthCheckResponse call() {
HealthCheckResponseBuilder builder = HealthCheckResponse.named("Database connections health check").up();
for (Map.Entry<String, DataSource> 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() + "'";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,14 +20,14 @@ public class DataSourcesExcludedFromHealthChecksProcessor {
@Record(STATIC_INIT)
void produceBean(
Capabilities capabilities,
DataSourcesExcludedFromHealthChecksRecorder recorder,
DataSourcesHealthSupportRecorder recorder,
DataSourcesBuildTimeConfig config,
BuildProducer<SyntheticBeanBuildItem> 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());
}
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<String> configuredNames;
private final Set<String> excludedNames;

public DataSourcesHealthSupport(Set<String> configuredNames, Set<String> excludedNames) {
this.configuredNames = configuredNames;
this.excludedNames = excludedNames;
}

public Set<String> getConfiguredNames() {
return configuredNames;
}

public Set<String> getExcludedNames() {
return excludedNames;
}
}
Original file line number Diff line number Diff line change
@@ -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<DataSourcesHealthSupport> configureDataSourcesHealthSupport(
DataSourcesBuildTimeConfig config) {
Stream.Builder<String> configured = Stream.builder();
Stream.Builder<String> 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<String, DataSourceBuildTimeConfig> dataSource : config.namedDataSources.entrySet()) {
configured.add(dataSource.getKey());
if (dataSource.getValue().healthExclude) {
excluded.add(dataSource.getKey());
}
}
Set<String> names = configured.build().collect(toUnmodifiableSet());
Set<String> excludedNames = excluded.build().collect(toUnmodifiableSet());
return new RuntimeValue<>(new DataSourcesHealthSupport(names, excludedNames));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String> excludedNames = excluded.getExcludedNames();
for (InstanceHandle<DB2Pool> handle : container.select(DB2Pool.class, Any.Literal.INSTANCE).handles()) {
String db2PoolName = getDB2PoolName(handle.getBean());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String> excludedNames = excluded.getExcludedNames();
for (InstanceHandle<MSSQLPool> handle : container.select(MSSQLPool.class, Any.Literal.INSTANCE).handles()) {
String poolName = getPoolName(handle.getBean());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String> excludedNames = excluded.getExcludedNames();
for (InstanceHandle<MySQLPool> handle : container.select(MySQLPool.class, Any.Literal.INSTANCE).handles()) {
String poolName = getPoolName(handle.getBean());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String> excludedNames = excluded.getExcludedNames();
for (InstanceHandle<PgPool> handle : container.select(PgPool.class, Any.Literal.INSTANCE).handles()) {
String poolName = getPoolName(handle.getBean());
Expand Down