From c5138c56ff4c4fa7ec6c4ff2823e2b86bc7e0ef1 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 23 Oct 2019 15:04:06 -0700 Subject: [PATCH] Restore AbstractRoutingDataSource health support Update `DataSourceHealthContributorAutoConfiguration` so that any `AbstractRoutingDataSource` beans are still included in the overall health. Prior to this commit, a regression in Spring Boot 2.2 meant that if a single routing bean was found an `IllegalArgumentException` would be thrown. In Spring Boot 2.1 all `AbstractRoutingDataSource` would be filtered from the results, but if no results existed the following was returned: "details": { "db": { "status": "UNKNOWN" }, In Spring Boot 2.2 we now always include routing datasource beans, even if other non-routing database beans are found. The health details includes `"routing" : true` to help users disambiguate any results. Fixes gh-18661 --- ...rceHealthContributorAutoConfiguration.java | 39 +++++++++++-------- ...althContributorAutoConfigurationTests.java | 17 ++++++-- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java index 5446614b529..885c4e57de5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.autoconfigure.jdbc; import java.util.Collection; -import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; @@ -27,7 +26,10 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health.Builder; import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -60,7 +62,7 @@ import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; @ConditionalOnEnabledHealthIndicator("db") @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class DataSourceHealthContributorAutoConfiguration extends - CompositeHealthContributorConfiguration implements InitializingBean { + CompositeHealthContributorConfiguration implements InitializingBean { private final Collection metadataProviders; @@ -79,24 +81,14 @@ public class DataSourceHealthContributorAutoConfiguration extends @Bean @ConditionalOnMissingBean(name = { "dbHealthIndicator", "dbHealthContributor" }) public HealthContributor dbHealthContributor(Map dataSources) { - return createContributor(filterDataSources(dataSources)); - } - - private Map filterDataSources(Map candidates) { - if (candidates == null) { - return null; - } - Map dataSources = new LinkedHashMap<>(); - candidates.forEach((name, dataSource) -> { - if (!(dataSource instanceof AbstractRoutingDataSource)) { - dataSources.put(name, dataSource); - } - }); - return dataSources; + return createContributor(dataSources); } @Override - protected DataSourceHealthIndicator createIndicator(DataSource source) { + protected AbstractHealthIndicator createIndicator(DataSource source) { + if (source instanceof AbstractRoutingDataSource) { + return new RoutingDataSourceHealthIndicator(); + } return new DataSourceHealthIndicator(source, getValidationQuery(source)); } @@ -105,4 +97,17 @@ public class DataSourceHealthContributorAutoConfiguration extends return (poolMetadata != null) ? poolMetadata.getValidationQuery() : null; } + /** + * {@link HealthIndicator} used for {@link AbstractRoutingDataSource} beans where we + * can't actually query for the status. + */ + static class RoutingDataSourceHealthIndicator extends AbstractHealthIndicator { + + @Override + protected void doHealthCheck(Builder builder) throws Exception { + builder.unknown().withDetail("routing", true); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java index 49ba404a1f4..080d03cfa4a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java @@ -21,6 +21,7 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration.RoutingDataSourceHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthContributor; import org.springframework.boot.actuate.health.NamedContributor; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; @@ -71,10 +72,20 @@ class DataSourceHealthContributorAutoConfigurationTests { } @Test - void runShouldFilterRoutingDataSource() { + void runWithRoutingAndEmbeddedDataSourceShouldFilterRoutingDataSource() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, RoutingDatasourceConfig.class) - .run((context) -> assertThat(context).hasSingleBean(DataSourceHealthIndicator.class) - .doesNotHaveBean(CompositeHealthContributor.class)); + .run((context) -> { + CompositeHealthContributor composite = context.getBean(CompositeHealthContributor.class); + assertThat(composite.getContributor("dataSource")).isInstanceOf(DataSourceHealthIndicator.class); + assertThat(composite.getContributor("routingDataSource")) + .isInstanceOf(RoutingDataSourceHealthIndicator.class); + }); + } + + @Test + void runWithOnlyRoutingDataSourceShouldFilterRoutingDataSource() { + this.contextRunner.withUserConfiguration(RoutingDatasourceConfig.class) + .run((context) -> assertThat(context).hasSingleBean(RoutingDataSourceHealthIndicator.class)); } @Test