Provide health for an AbstractRoutingDataSource's resolved targets

See gh-25708
This commit is contained in:
bono007 2021-03-16 22:52:53 -05:00 committed by Andy Wilkinson
parent 710a905187
commit 13600c3367
2 changed files with 57 additions and 18 deletions

View File

@ -17,19 +17,19 @@
package org.springframework.boot.actuate.autoconfigure.jdbc; package org.springframework.boot.actuate.autoconfigure.jdbc;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider; 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.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.HealthContributor; import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -45,6 +45,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.Assert;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for * {@link EnableAutoConfiguration Auto-configuration} for
@ -64,8 +65,7 @@ import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
@ConditionalOnEnabledHealthIndicator("db") @ConditionalOnEnabledHealthIndicator("db")
@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(DataSourceHealthIndicatorProperties.class) @EnableConfigurationProperties(DataSourceHealthIndicatorProperties.class)
public class DataSourceHealthContributorAutoConfiguration extends public class DataSourceHealthContributorAutoConfiguration implements InitializingBean {
CompositeHealthContributorConfiguration<AbstractHealthIndicator, DataSource> implements InitializingBean {
private final Collection<DataSourcePoolMetadataProvider> metadataProviders; private final Collection<DataSourcePoolMetadataProvider> metadataProviders;
@ -94,10 +94,18 @@ public class DataSourceHealthContributorAutoConfiguration extends
return createContributor(dataSources); return createContributor(dataSources);
} }
@Override private HealthContributor createContributor(Map<String, DataSource> beans) {
protected AbstractHealthIndicator createIndicator(DataSource source) { Assert.notEmpty(beans, "Beans must not be empty");
if (beans.size() == 1) {
return createIndicator(beans.values().iterator().next());
}
return CompositeHealthContributor.fromMap(beans, this::createIndicator);
}
private HealthContributor createIndicator(DataSource source) {
if (source instanceof AbstractRoutingDataSource) { if (source instanceof AbstractRoutingDataSource) {
return new RoutingDataSourceHealthIndicator(); AbstractRoutingDataSource routingDataSource = (AbstractRoutingDataSource) source;
return new RoutingDataSourceHealthIndicator(routingDataSource, this::createIndicator);
} }
return new DataSourceHealthIndicator(source, getValidationQuery(source)); return new DataSourceHealthIndicator(source, getValidationQuery(source));
} }
@ -108,14 +116,29 @@ public class DataSourceHealthContributorAutoConfiguration extends
} }
/** /**
* {@link HealthIndicator} used for {@link AbstractRoutingDataSource} beans where we * {@link CompositeHealthContributor} used for {@link AbstractRoutingDataSource} beans
* can't actually query for the status. * where the overall health is composed of a {@link DataSourceHealthIndicator} for
* each routed datasource.
*/ */
static class RoutingDataSourceHealthIndicator extends AbstractHealthIndicator { static class RoutingDataSourceHealthIndicator implements CompositeHealthContributor {
private CompositeHealthContributor delegate;
RoutingDataSourceHealthIndicator(AbstractRoutingDataSource routingDataSource,
Function<DataSource, HealthContributor> indicatorFunction) {
Map<String, DataSource> routedDataSources = routingDataSource.getResolvedDataSources().entrySet().stream()
.collect(Collectors.toMap((e) -> e.getKey().toString(), Map.Entry::getValue));
this.delegate = CompositeHealthContributor.fromMap(routedDataSources, indicatorFunction);
}
@Override @Override
protected void doHealthCheck(Builder builder) throws Exception { public HealthContributor getContributor(String name) {
builder.unknown().withDetail("routing", true); return this.delegate.getContributor(name);
}
@Override
public Iterator<NamedContributor<HealthContributor>> iterator() {
return this.delegate.iterator();
} }
} }

View File

@ -16,6 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.jdbc; package org.springframework.boot.actuate.autoconfigure.jdbc;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -38,6 +41,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
@ -93,9 +97,16 @@ class DataSourceHealthContributorAutoConfigurationTests {
} }
@Test @Test
void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSource() { void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators() {
this.contextRunner.withUserConfiguration(RoutingDataSourceConfig.class) this.contextRunner.withUserConfiguration(RoutingDataSourceConfig.class).run((context) -> {
.run((context) -> assertThat(context).hasSingleBean(RoutingDataSourceHealthIndicator.class)); assertThat(context).hasSingleBean(RoutingDataSourceHealthIndicator.class);
RoutingDataSourceHealthIndicator routingHealthContributor = context
.getBean(RoutingDataSourceHealthIndicator.class);
assertThat(routingHealthContributor.getContributor("one")).isInstanceOf(DataSourceHealthIndicator.class);
assertThat(routingHealthContributor.getContributor("two")).isInstanceOf(DataSourceHealthIndicator.class);
assertThat(routingHealthContributor.iterator()).toIterable().extracting("name")
.containsExactlyInAnyOrder("one", "two");
});
} }
@Test @Test
@ -143,7 +154,12 @@ class DataSourceHealthContributorAutoConfigurationTests {
@Bean @Bean
AbstractRoutingDataSource routingDataSource() { AbstractRoutingDataSource routingDataSource() {
return mock(AbstractRoutingDataSource.class); Map<Object, DataSource> dataSources = new HashMap<>();
dataSources.put("one", mock(DataSource.class));
dataSources.put("two", mock(DataSource.class));
AbstractRoutingDataSource routingDataSource = mock(AbstractRoutingDataSource.class);
given(routingDataSource.getResolvedDataSources()).willReturn(dataSources);
return routingDataSource;
} }
} }