From 13600c33676f1399bbc4bec007f91aa2e7d0d4dd Mon Sep 17 00:00:00 2001 From: bono007 Date: Tue, 16 Mar 2021 22:52:53 -0500 Subject: [PATCH 1/2] Provide health for an AbstractRoutingDataSource's resolved targets See gh-25708 --- ...rceHealthContributorAutoConfiguration.java | 51 ++++++++++++++----- ...althContributorAutoConfigurationTests.java | 24 +++++++-- 2 files changed, 57 insertions(+), 18 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 73fe9e708b..9749864cc9 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,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 implements InitializingBean { +public class DataSourceHealthContributorAutoConfiguration implements InitializingBean { private final Collection metadataProviders; @@ -94,10 +94,18 @@ public class DataSourceHealthContributorAutoConfiguration extends return createContributor(dataSources); } - @Override - protected AbstractHealthIndicator createIndicator(DataSource source) { + private HealthContributor createContributor(Map 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 indicatorFunction) { + Map 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> iterator() { + return this.delegate.iterator(); } } 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 1950cec915..7a93026772 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 @@ -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 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; } } From 0a8da4fd7dccf7876d478cebd55ab7f678f4ef9c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Sat, 10 Apr 2021 10:44:08 +0100 Subject: [PATCH 2/2] Polish "Provide health for an AbstractRoutingDataSource's resolved targets" See gh-25708 --- .../DataSourceHealthContributorAutoConfiguration.java | 2 +- .../src/docs/asciidoc/production-ready-features.adoc | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) 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 9749864cc9..bd9c25f03f 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 @@ -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. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc index a94d17173d..b2ec37d4f2 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc @@ -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].