Skip to content

Provide a way to run HealthIndicators concurrently #17617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthIndicatorStrategy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
Expand All @@ -38,8 +39,9 @@ class HealthEndpointConfiguration {

@Bean
@ConditionalOnMissingBean
HealthEndpoint healthEndpoint(HealthAggregator healthAggregator, HealthIndicatorRegistry registry) {
return new HealthEndpoint(new CompositeHealthIndicator(healthAggregator, registry));
HealthEndpoint healthEndpoint(HealthAggregator healthAggregator, HealthIndicatorRegistry registry,
HealthIndicatorStrategy healthIndicatorStrategy) {
return new HealthEndpoint(new CompositeHealthIndicator(healthAggregator, registry, healthIndicatorStrategy));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import reactor.core.publisher.Flux;

import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorStrategy;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthIndicatorStrategy;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry;
Expand Down Expand Up @@ -71,6 +73,12 @@ public HealthIndicatorRegistry healthIndicatorRegistry(ApplicationContext applic
return HealthIndicatorRegistryBeans.get(applicationContext);
}

@Bean
@ConditionalOnMissingBean
public HealthIndicatorStrategy healthIndicatorStrategy() {
return new DefaultHealthIndicatorStrategy();
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Flux.class)
static class ReactiveHealthIndicatorConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@

package org.springframework.boot.actuate.autoconfigure.health;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;

import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorStrategy;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorStrategy;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.AutoConfigurations;
Expand Down Expand Up @@ -81,6 +84,18 @@ void runShouldCreateOrderedHealthAggregator() {
.isInstanceOf(OrderedHealthAggregator.class));
}

@Test
void shouldCreateDefaultHealthIndicatorStrategy() {
this.contextRunner.run((context) -> assertThat(context).getBean(HealthIndicatorStrategy.class)
.isInstanceOf(DefaultHealthIndicatorStrategy.class));
}

@Test
void shouldNotCreateDefaultHealthIndicatorCustomStrategyPresent() {
this.contextRunner.withBean(CustomHealthIndicatorStrategy.class).run((context) -> assertThat(context)
.getBean(HealthIndicatorStrategy.class).isInstanceOf(CustomHealthIndicatorStrategy.class));
}

@Test
void runWhenHasCustomOrderPropertyShouldCreateOrderedHealthAggregator() {
this.contextRunner.withPropertyValues("management.health.status.order:UP,DOWN").run((context) -> {
Expand Down Expand Up @@ -111,6 +126,15 @@ HealthIndicator customHealthIndicator() {

}

static class CustomHealthIndicatorStrategy implements HealthIndicatorStrategy {

@Override
public Map<String, Health> doHealth(Map<String, HealthIndicator> healthIndicators) {
return Collections.emptyMap();
}

}

static class CustomHealthIndicator implements HealthIndicator {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@

package org.springframework.boot.actuate.health;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* {@link HealthIndicator} that returns health indications from all registered delegates.
* {@link HealthIndicator} that returns health indications from all registered delegates
* using the {@link HealthIndicatorStrategy} and aggregates the result via
* {@link HealthAggregator} into a final one.
*
* @author Tyler J. Frederick
* @author Phillip Webb
Expand All @@ -33,6 +34,8 @@ public class CompositeHealthIndicator implements HealthIndicator {

private final HealthAggregator aggregator;

private final HealthIndicatorStrategy strategy;

/**
* Create a new {@link CompositeHealthIndicator} from the specified indicators.
* @param healthAggregator the health aggregator
Expand All @@ -50,8 +53,23 @@ public CompositeHealthIndicator(HealthAggregator healthAggregator, Map<String, H
* @param registry the registry of {@link HealthIndicator HealthIndicators}.
*/
public CompositeHealthIndicator(HealthAggregator healthAggregator, HealthIndicatorRegistry registry) {
this(healthAggregator, registry, new DefaultHealthIndicatorStrategy());
}

/**
* Create a new {@link CompositeHealthIndicator} from the indicators in the given
* {@code registry}.
* @param healthAggregator the health aggregator
* @param registry the registry of {@link HealthIndicator HealthIndicators}.
* @param strategy the strategy how {@link HealthIndicator HealthIndicator} instnaces
* should be called.
* @since 2.2.0
*/
public CompositeHealthIndicator(HealthAggregator healthAggregator, HealthIndicatorRegistry registry,
HealthIndicatorStrategy strategy) {
this.aggregator = healthAggregator;
this.registry = registry;
this.strategy = strategy;
}

/**
Expand All @@ -65,10 +83,7 @@ public HealthIndicatorRegistry getRegistry() {

@Override
public Health health() {
Map<String, Health> healths = new LinkedHashMap<>();
for (Map.Entry<String, HealthIndicator> entry : this.registry.getAll().entrySet()) {
healths.put(entry.getKey(), entry.getValue().health());
}
Map<String, Health> healths = this.strategy.doHealth(this.registry.getAll());
return this.aggregator.aggregate(healths);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2012-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.health;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
*
* {@link HealthIndicatorStrategy} that returns health indications from all
* {@link HealthIndicator} instances concurrently.
*
* @author Dmytro Nosan
* @since 2.2.0
*/
public class ConcurrentlyHealthIndicatorStrategy implements HealthIndicatorStrategy {

private static final Health UNKNOWN = Health.unknown().build();

private final Executor executor;

private final Long timeout;

private final Health timeoutHealth;

/**
* Returns a new {@link ConcurrentlyHealthIndicatorStrategy} with no timeout.
* @param executor the executor to submit {@link HealthIndicator HealthIndicators} on.
*/
public ConcurrentlyHealthIndicatorStrategy(Executor executor) {
this.executor = executor;
this.timeout = null;
this.timeoutHealth = null;
}

/**
* Returns a new {@link ConcurrentlyHealthIndicatorStrategy} with timeout.
* @param executor the executor to submit {@link HealthIndicator HealthIndicators} on.
* @param timeout number of milliseconds to wait before using the
* {@code timeoutHealth}
* @param timeoutHealth the {@link Health} to use if an health indicator reached the
* {@code timeout}. Defaults to {@code unknown} status.
*/
public ConcurrentlyHealthIndicatorStrategy(Executor executor, long timeout, Health timeoutHealth) {
this.executor = executor;
this.timeout = timeout;
this.timeoutHealth = (timeoutHealth != null) ? timeoutHealth : UNKNOWN;
}

@Override
public Map<String, Health> doHealth(Map<String, HealthIndicator> healthIndicators) {
Map<String, Future<Health>> healthsFutures = new LinkedHashMap<>();
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthsFutures.put(entry.getKey(), CompletableFuture.supplyAsync(entry.getValue()::health, this.executor));
}
Map<String, Health> healths = new LinkedHashMap<>();
for (Map.Entry<String, Future<Health>> entry : healthsFutures.entrySet()) {
healths.put(entry.getKey(), getHealth(entry.getValue(), this.timeout, this.timeoutHealth));
}
return healths;
}

private static Health getHealth(Future<Health> healthFuture, Long timeout, Health timeoutHealth) {
try {
return (timeout != null) ? healthFuture.get(timeout, TimeUnit.MILLISECONDS) : healthFuture.get();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return Health.unknown().withException(ex).build();
}
catch (TimeoutException ex) {
return timeoutHealth;
}
catch (ExecutionException ex) {
Throwable cause = ex.getCause();
return Health.down((cause instanceof Exception) ? ((Exception) cause) : ex).build();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2012-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.health;

import java.util.LinkedHashMap;
import java.util.Map;

/**
*
* {@link HealthIndicatorStrategy} that returns health indications from all
* {@link HealthIndicator} instances sequentially.
*
* @author Dmytro Nosan
* @since 2.2.0
*/
public class DefaultHealthIndicatorStrategy implements HealthIndicatorStrategy {

@Override
public Map<String, Health> doHealth(Map<String, HealthIndicator> healthIndicators) {
Map<String, Health> healths = new LinkedHashMap<>();
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healths.put(entry.getKey(), entry.getValue().health());
}
return healths;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2012-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.health;

import java.util.Map;

/**
* Strategy interface used to define how {@link HealthIndicator} instances should be
* called.
*
* @author Dmytro Nosan
* @since 2.2.0
* @see DefaultHealthIndicatorStrategy
* @see ConcurrentlyHealthIndicatorStrategy
*/
@FunctionalInterface
public interface HealthIndicatorStrategy {

/**
* Calls the given {@link HealthIndicator} instances.
* @param healthIndicators the {@link HealthIndicator} instances that should be called
* @return the health indications from all {@link HealthIndicator} instances
*/
Map<String, Health> doHealth(Map<String, HealthIndicator> healthIndicators);

}
Loading