Browse Source

Consider reactive indicators in HealthEndpointGroupMembershipValidator

Update `HealthEndpointGroupMembershipValidator` to also consider
reactive health indicators.

Fixes gh-48387
pull/48396/head
Phillip Webb 2 weeks ago
parent
commit
739b92e525
  1. 36
      module/spring-boot-health/src/main/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointConfiguration.java
  2. 38
      module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointAutoConfigurationTests.java

36
module/spring-boot-health/src/main/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointConfiguration.java

@ -17,6 +17,7 @@
package org.springframework.boot.health.autoconfigure.actuate.endpoint; package org.springframework.boot.health.autoconfigure.actuate.endpoint;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@ -34,6 +35,7 @@ import org.springframework.boot.health.actuate.endpoint.SimpleHttpCodeStatusMapp
import org.springframework.boot.health.actuate.endpoint.SimpleStatusAggregator; import org.springframework.boot.health.actuate.endpoint.SimpleStatusAggregator;
import org.springframework.boot.health.actuate.endpoint.StatusAggregator; import org.springframework.boot.health.actuate.endpoint.StatusAggregator;
import org.springframework.boot.health.contributor.HealthContributors; import org.springframework.boot.health.contributor.HealthContributors;
import org.springframework.boot.health.contributor.ReactiveHealthContributors;
import org.springframework.boot.health.registry.HealthContributorRegistry; import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry; import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -78,8 +80,10 @@ class HealthEndpointConfiguration {
@Bean @Bean
@ConditionalOnBooleanProperty(name = "management.endpoint.health.validate-group-membership", matchIfMissing = true) @ConditionalOnBooleanProperty(name = "management.endpoint.health.validate-group-membership", matchIfMissing = true)
HealthEndpointGroupMembershipValidator healthEndpointGroupMembershipValidator(HealthEndpointProperties properties, HealthEndpointGroupMembershipValidator healthEndpointGroupMembershipValidator(HealthEndpointProperties properties,
HealthContributorRegistry healthContributorRegistry) { HealthContributorRegistry healthContributorRegistry,
return new HealthEndpointGroupMembershipValidator(properties, healthContributorRegistry); ObjectProvider<ReactiveHealthContributorRegistry> reactiveHealthContributorRegistry) {
return new HealthEndpointGroupMembershipValidator(properties, healthContributorRegistry,
reactiveHealthContributorRegistry.getIfAvailable());
} }
@Bean @Bean
@ -138,10 +142,13 @@ class HealthEndpointConfiguration {
private final HealthContributorRegistry registry; private final HealthContributorRegistry registry;
HealthEndpointGroupMembershipValidator(HealthEndpointProperties properties, @Nullable ReactiveHealthContributorRegistry fallbackRegistry;
HealthContributorRegistry registry) {
HealthEndpointGroupMembershipValidator(HealthEndpointProperties properties, HealthContributorRegistry registry,
@Nullable ReactiveHealthContributorRegistry fallbackRegistry) {
this.properties = properties; this.properties = properties;
this.registry = registry; this.registry = registry;
this.fallbackRegistry = fallbackRegistry;
} }
@Override @Override
@ -172,13 +179,28 @@ class HealthEndpointConfiguration {
} }
private boolean contributorExists(String[] path) { private boolean contributorExists(String[] path) {
return contributorExistsInMainRegistry(path) || contributorExistsInFallbackRegistry(path);
}
private boolean contributorExistsInMainRegistry(String[] path) {
return contributorExists(path, this.registry, HealthContributors.class, HealthContributors::getContributor);
}
private boolean contributorExistsInFallbackRegistry(String[] path) {
return contributorExists(path, this.fallbackRegistry, ReactiveHealthContributors.class,
ReactiveHealthContributors::getContributor);
}
@SuppressWarnings("unchecked")
private <C> boolean contributorExists(String[] path, @Nullable Object registry, Class<C> collectionType,
BiFunction<C, String, Object> getFromCollection) {
int pathOffset = 0; int pathOffset = 0;
Object contributor = this.registry; Object contributor = registry;
while (pathOffset < path.length) { while (pathOffset < path.length) {
if (!(contributor instanceof HealthContributors)) { if (contributor == null || !collectionType.isInstance(contributor)) {
return false; return false;
} }
contributor = ((HealthContributors) contributor).getContributor(path[pathOffset]); contributor = getFromCollection.apply((C) contributor, path[pathOffset]);
pathOffset++; pathOffset++;
} }
return (contributor != null); return (contributor != null);

38
module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointAutoConfigurationTests.java

@ -44,6 +44,7 @@ import org.springframework.boot.health.autoconfigure.actuate.endpoint.HealthEndp
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration; import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration; import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration;
import org.springframework.boot.health.contributor.CompositeHealthContributor; import org.springframework.boot.health.contributor.CompositeHealthContributor;
import org.springframework.boot.health.contributor.CompositeReactiveHealthContributor;
import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributors; import org.springframework.boot.health.contributor.HealthContributors;
import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.boot.health.contributor.HealthIndicator;
@ -145,6 +146,20 @@ class HealthEndpointAutoConfigurationTests {
}); });
} }
@Test
void runDoesNotFailWhenHealthEndpointGroupIncludesContributorThatExists() {
this.contextRunner.withUserConfiguration(CompositeHealthIndicatorConfiguration.class)
.withPropertyValues("management.endpoint.health.group.ready.include=composite/b/c")
.run((context) -> assertThat(context).hasNotFailed());
}
@Test // gh-48387
void runDoesNotFailWhenHealthEndpointGroupIncludesReactiveContributorThatExists() {
this.contextRunner.withUserConfiguration(CompositeReactiveHealthIndicatorConfiguration.class)
.withPropertyValues("management.endpoint.health.group.ready.include=composite/b/c")
.run((context) -> assertThat(context).hasNotFailed());
}
@Test @Test
void runFailsWhenHealthEndpointGroupIncludesContributorThatDoesNotExist() { void runFailsWhenHealthEndpointGroupIncludesContributorThatDoesNotExist() {
this.contextRunner.withUserConfiguration(CompositeHealthIndicatorConfiguration.class) this.contextRunner.withUserConfiguration(CompositeHealthIndicatorConfiguration.class)
@ -377,8 +392,27 @@ class HealthEndpointAutoConfigurationTests {
@Bean @Bean
CompositeHealthContributor compositeHealthIndicator() { CompositeHealthContributor compositeHealthIndicator() {
return CompositeHealthContributor.fromMap(Map.of("a", (HealthIndicator) () -> Health.up().build(), "b", return CompositeHealthContributor.fromMap(Map.of("a", createHealthIndicator(), "b",
CompositeHealthContributor.fromMap(Map.of("c", (HealthIndicator) () -> Health.up().build())))); CompositeHealthContributor.fromMap(Map.of("c", createHealthIndicator()))));
}
private HealthIndicator createHealthIndicator() {
return () -> Health.up().build();
}
}
@Configuration(proxyBeanMethods = false)
static class CompositeReactiveHealthIndicatorConfiguration {
@Bean
CompositeReactiveHealthContributor compositeHealthIndicator() {
return CompositeReactiveHealthContributor.fromMap(Map.of("a", createHealthIndicator(), "b",
CompositeReactiveHealthContributor.fromMap(Map.of("c", createHealthIndicator()))));
}
private ReactiveHealthIndicator createHealthIndicator() {
return () -> Mono.just(Health.up().build());
} }
} }

Loading…
Cancel
Save