Merge pull request #25708 from bono007
* gh-25708: Polish "Provide health for an AbstractRoutingDataSource's resolved targets" Provide health for an AbstractRoutingDataSource's resolved targets Closes gh-25708
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,19 +17,19 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.jdbc;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
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.CompositeHealthContributor;
|
||||
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.autoconfigure.AutoConfigureAfter;
|
||||
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.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for
|
||||
@@ -64,8 +65,7 @@ import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
@ConditionalOnEnabledHealthIndicator("db")
|
||||
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(DataSourceHealthIndicatorProperties.class)
|
||||
public class DataSourceHealthContributorAutoConfiguration extends
|
||||
CompositeHealthContributorConfiguration<AbstractHealthIndicator, DataSource> implements InitializingBean {
|
||||
public class DataSourceHealthContributorAutoConfiguration implements InitializingBean {
|
||||
|
||||
private final Collection<DataSourcePoolMetadataProvider> metadataProviders;
|
||||
|
||||
@@ -94,10 +94,18 @@ public class DataSourceHealthContributorAutoConfiguration extends
|
||||
return createContributor(dataSources);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractHealthIndicator createIndicator(DataSource source) {
|
||||
private HealthContributor createContributor(Map<String, DataSource> beans) {
|
||||
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) {
|
||||
return new RoutingDataSourceHealthIndicator();
|
||||
AbstractRoutingDataSource routingDataSource = (AbstractRoutingDataSource) source;
|
||||
return new RoutingDataSourceHealthIndicator(routingDataSource, this::createIndicator);
|
||||
}
|
||||
return new DataSourceHealthIndicator(source, getValidationQuery(source));
|
||||
}
|
||||
@@ -108,14 +116,29 @@ public class DataSourceHealthContributorAutoConfiguration extends
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HealthIndicator} used for {@link AbstractRoutingDataSource} beans where we
|
||||
* can't actually query for the status.
|
||||
* {@link CompositeHealthContributor} used for {@link AbstractRoutingDataSource} beans
|
||||
* 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
|
||||
protected void doHealthCheck(Builder builder) throws Exception {
|
||||
builder.unknown().withDetail("routing", true);
|
||||
public HealthContributor getContributor(String name) {
|
||||
return this.delegate.getContributor(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<NamedContributor<HealthContributor>> iterator() {
|
||||
return this.delegate.iterator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.jdbc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -38,6 +41,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@@ -93,9 +97,16 @@ class DataSourceHealthContributorAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSource() {
|
||||
this.contextRunner.withUserConfiguration(RoutingDataSourceConfig.class)
|
||||
.run((context) -> assertThat(context).hasSingleBean(RoutingDataSourceHealthIndicator.class));
|
||||
void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators() {
|
||||
this.contextRunner.withUserConfiguration(RoutingDataSourceConfig.class).run((context) -> {
|
||||
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
|
||||
@@ -143,7 +154,12 @@ class DataSourceHealthContributorAutoConfigurationTests {
|
||||
|
||||
@Bean
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -989,6 +989,15 @@ TIP: You can use `@Qualifier("groupname")` if you need to register custom `Statu
|
||||
|
||||
|
||||
|
||||
[[production-ready-health-datasource]]
|
||||
==== DataSource Health
|
||||
The `DataSource` health indicator shows the health of both standard data source and routing data source beans.
|
||||
The health of a routing data source includes the health of each of its target data sources.
|
||||
In the health endpoint's response, each of a routing data source's targets is named using its routing key.
|
||||
If you prefer not to include routing data sources in the indicator's output, set configprop:management.health.db.ignore-routing-data-sources[] to `true`.
|
||||
|
||||
|
||||
|
||||
[[production-ready-kubernetes-probes]]
|
||||
=== Kubernetes Probes
|
||||
Applications deployed on Kubernetes can provide information about their internal state with https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes[Container Probes].
|
||||
|
||||
Reference in New Issue
Block a user