Get health from composite indicators (#4074)

This commit is contained in:
Olga Maciaszek-Sharma
2022-03-17 12:25:57 +01:00
committed by GitHub
parent eafecd3b6a
commit 1efa843ed8
2 changed files with 160 additions and 49 deletions

View File

@@ -19,19 +19,21 @@ package org.springframework.cloud.netflix.eureka;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.Health;
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.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.actuate.health.StatusAggregator;
@@ -58,6 +60,7 @@ import org.springframework.util.Assert;
* @author Spencer Gibb
* @author Nowrin Anwar Joyita
* @author Bertrand Renuart
* @author Olga Maciaszek-Sharma
* @see HealthCheckHandler
* @see StatusAggregator
*/
@@ -77,14 +80,14 @@ public class EurekaHealthCheckHandler
private ApplicationContext applicationContext;
private Map<String, HealthIndicator> healthIndicators = new HashMap<>();
private Map<String, HealthContributor> healthContributors = new HashMap<>();
/**
* {@code true} until the context is stopped.
*/
private boolean running = true;
private Map<String, ReactiveHealthIndicator> reactiveHealthIndicators = new HashMap<>();
private Map<String, ReactiveHealthContributor> reactiveHealthContributors = new HashMap<>();
public EurekaHealthCheckHandler(StatusAggregator statusAggregator) {
this.statusAggregator = statusAggregator;
@@ -99,39 +102,28 @@ public class EurekaHealthCheckHandler
@Override
public void afterPropertiesSet() {
final Map<String, HealthIndicator> healthIndicators = applicationContext.getBeansOfType(HealthIndicator.class);
final Map<String, ReactiveHealthIndicator> reactiveHealthIndicators = applicationContext
.getBeansOfType(ReactiveHealthIndicator.class);
populateHealthIndicators(healthIndicators);
populateReactiveHealthIndicators(reactiveHealthIndicators);
populateHealthContributors(applicationContext.getBeansOfType(HealthContributor.class));
reactiveHealthContributors.putAll(applicationContext.getBeansOfType(ReactiveHealthContributor.class));
}
void populateHealthIndicators(Map<String, HealthIndicator> healthIndicators) {
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
void populateHealthContributors(Map<String, HealthContributor> healthContributors) {
for (Map.Entry<String, HealthContributor> entry : healthContributors.entrySet()) {
// ignore EurekaHealthIndicator and flatten the rest of the composite
// otherwise there is a never ending cycle of down. See gh-643
if (entry.getValue() instanceof DiscoveryCompositeHealthContributor) {
DiscoveryCompositeHealthContributor indicator = (DiscoveryCompositeHealthContributor) entry.getValue();
indicator.forEach(contributor -> {
if (!(contributor.getContributor() instanceof EurekaHealthIndicator)) {
this.healthIndicators.put(contributor.getName(),
(HealthIndicator) contributor.getContributor());
indicator.getIndicators().forEach((name, discoveryHealthIndicator) -> {
if (!(discoveryHealthIndicator instanceof EurekaHealthIndicator)) {
this.healthContributors.put(name, (HealthIndicator) discoveryHealthIndicator::health);
}
});
}
else {
this.healthIndicators.put(entry.getKey(), entry.getValue());
this.healthContributors.put(entry.getKey(), entry.getValue());
}
}
}
void populateReactiveHealthIndicators(Map<String, ReactiveHealthIndicator> reactiveHealthIndicators) {
for (Map.Entry<String, ReactiveHealthIndicator> entry : reactiveHealthIndicators.entrySet()) {
this.reactiveHealthIndicators.put(entry.getKey(), entry.getValue());
}
}
@Override
public InstanceStatus getStatus(InstanceStatus instanceStatus) {
if (running) {
@@ -151,21 +143,39 @@ public class EurekaHealthCheckHandler
}
protected Status getStatus(StatusAggregator statusAggregator) {
Status status;
Set<Status> statusSet = new HashSet<>();
if (healthIndicators != null) {
statusSet.addAll(healthIndicators.values().stream().map(HealthIndicator::health).map(Health::getStatus)
.collect(Collectors.toSet()));
for (HealthContributor contributor : healthContributors.values()) {
processContributor(statusSet, contributor);
}
if (reactiveHealthIndicators != null) {
statusSet.addAll(reactiveHealthIndicators.values().stream().map(ReactiveHealthIndicator::health)
.map(Mono::block).filter(Objects::nonNull).map(Health::getStatus).collect(Collectors.toSet()));
for (ReactiveHealthContributor contributor : reactiveHealthContributors.values()) {
processContributor(statusSet, contributor);
}
return statusAggregator.getAggregateStatus(statusSet);
}
status = statusAggregator.getAggregateStatus(statusSet);
return status;
private void processContributor(Set<Status> statusSet, HealthContributor contributor) {
if (contributor instanceof CompositeHealthContributor) {
for (NamedContributor<HealthContributor> contrib : (CompositeHealthContributor) contributor) {
processContributor(statusSet, contrib.getContributor());
}
}
else if (contributor instanceof HealthIndicator) {
statusSet.add(((HealthIndicator) contributor).health().getStatus());
}
}
private void processContributor(Set<Status> statusSet, ReactiveHealthContributor contributor) {
if (contributor instanceof CompositeReactiveHealthContributor) {
for (NamedContributor<ReactiveHealthContributor> contrib : (CompositeReactiveHealthContributor) contributor) {
processContributor(statusSet, contrib.getContributor());
}
}
else if (contributor instanceof ReactiveHealthIndicator) {
Health health = ((ReactiveHealthIndicator) contributor).health().block();
if (health != null) {
statusSet.add(health.getStatus());
}
}
}
protected InstanceStatus mapToInstanceStatus(Status status) {

View File

@@ -16,18 +16,23 @@
package org.springframework.cloud.netflix.eureka;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.Health;
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.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator;
@@ -44,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Jakub Narloch
* @author Nowrin Anwar Joyita
* @author Olga Maciaszek-Sharma
*/
class EurekaHealthCheckHandlerTests {
@@ -63,7 +69,7 @@ class EurekaHealthCheckHandlerTests {
}
@Test
void testAllUp() throws Exception {
void testAllUp() {
initialize(UpHealthConfiguration.class, ReactiveUpHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UNKNOWN);
@@ -71,7 +77,7 @@ class EurekaHealthCheckHandlerTests {
}
@Test
void testHealthCheckNotReturnedWhenStopped() throws Exception {
void testHealthCheckNotReturnedWhenStopped() {
initialize(UpHealthConfiguration.class);
healthCheckHandler.stop();
@@ -84,7 +90,7 @@ class EurekaHealthCheckHandlerTests {
}
@Test
void testDownWithBlockingIndicators() throws Exception {
void testDownWithBlockingIndicators() {
initialize(UpHealthConfiguration.class, DownHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UNKNOWN);
@@ -92,7 +98,7 @@ class EurekaHealthCheckHandlerTests {
}
@Test
void testDownWithReactiveIndicators() throws Exception {
void testDownWithReactiveIndicators() {
initialize(UpHealthConfiguration.class, ReactiveDownHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UNKNOWN);
@@ -100,7 +106,7 @@ class EurekaHealthCheckHandlerTests {
}
@Test
void testDownWhenBlockingIndicatorUpAndReactiveDown() throws Exception {
void testDownWhenBlockingIndicatorUpAndReactiveDown() {
initialize(ReactiveUpHealthConfiguration.class, DownHealthConfiguration.class);
InstanceStatus status = this.healthCheckHandler.getStatus(InstanceStatus.UNKNOWN);
@@ -108,7 +114,7 @@ class EurekaHealthCheckHandlerTests {
}
@Test
void testDownWhenBlockingIndicatorDownAndReactiveUp() throws Exception {
void testDownWhenBlockingIndicatorDownAndReactiveUp() {
initialize(ReactiveUpHealthConfiguration.class, ReactiveDownHealthConfiguration.class);
InstanceStatus status = this.healthCheckHandler.getStatus(InstanceStatus.UNKNOWN);
@@ -116,7 +122,7 @@ class EurekaHealthCheckHandlerTests {
}
@Test
void testUnknown() throws Exception {
void testUnknown() {
initialize(FatalHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UNKNOWN);
@@ -124,15 +130,38 @@ class EurekaHealthCheckHandlerTests {
}
@Test
@Disabled // FIXME: 3.0.0
void testEurekaIgnored() throws Exception {
void testEurekaIgnored() {
initialize(EurekaDownHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UP);
assertThat(status).isEqualTo(InstanceStatus.UP);
}
private void initialize(Class<?>... configurations) throws Exception {
@Test
void testCompositeComponentsDown() {
initialize(CompositeComponentsDownHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UP);
assertThat(status).isEqualTo(InstanceStatus.DOWN);
}
@Test
void testCompositeComponentsUp() {
initialize(CompositeComponentsUpHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UP);
assertThat(status).isEqualTo(InstanceStatus.UP);
}
@Test
void testCompositeComponentsOneDown() {
initialize(CompositeComponentsOneDownHealthConfiguration.class);
InstanceStatus status = healthCheckHandler.getStatus(InstanceStatus.UP);
assertThat(status).isEqualTo(InstanceStatus.DOWN);
}
private void initialize(Class<?>... configurations) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(configurations);
healthCheckHandler.setApplicationContext(applicationContext);
healthCheckHandler.afterPropertiesSet();
@@ -144,7 +173,7 @@ class EurekaHealthCheckHandlerTests {
public HealthIndicator healthIndicator() {
return new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
protected void doHealthCheck(Health.Builder builder) {
builder.up();
}
};
@@ -158,7 +187,7 @@ class EurekaHealthCheckHandlerTests {
public HealthIndicator healthIndicator() {
return new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
protected void doHealthCheck(Health.Builder builder) {
builder.down();
}
};
@@ -172,7 +201,7 @@ class EurekaHealthCheckHandlerTests {
public HealthIndicator healthIndicator() {
return new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
protected void doHealthCheck(Health.Builder builder) {
builder.status("fatal");
}
};
@@ -238,4 +267,76 @@ class EurekaHealthCheckHandlerTests {
}
protected static class CompositeComponentsDownHealthConfiguration {
@Bean
public CompositeHealthContributor compositeHealthContributor() {
return new TestCompositeHealthContributor(InstanceStatus.DOWN, InstanceStatus.DOWN);
}
}
protected static class CompositeComponentsUpHealthConfiguration {
@Bean
public CompositeHealthContributor compositeHealthContributor() {
return new TestCompositeHealthContributor(InstanceStatus.UP, InstanceStatus.UP);
}
}
protected static class CompositeComponentsOneDownHealthConfiguration {
@Bean
public CompositeHealthContributor compositeHealthContributor() {
return new TestCompositeHealthContributor(InstanceStatus.UP, InstanceStatus.DOWN);
}
}
static class TestCompositeHealthContributor implements CompositeHealthContributor {
private final Map<String, HealthContributor> contributorMap = new HashMap<>();
TestCompositeHealthContributor(InstanceStatus firstContributorStatus, InstanceStatus secondContributorStatus) {
contributorMap.put("first", new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) {
builder.status(firstContributorStatus.name());
}
});
contributorMap.put("second", new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) {
builder.status(secondContributorStatus.name());
}
});
}
@Override
public HealthContributor getContributor(String name) {
return contributorMap.get(name);
}
@Override
public Iterator<NamedContributor<HealthContributor>> iterator() {
Iterator<Map.Entry<String, HealthContributor>> iterator = contributorMap.entrySet().iterator();
return new Iterator<NamedContributor<HealthContributor>>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<HealthContributor> next() {
Map.Entry<String, HealthContributor> entry = iterator.next();
return NamedContributor.of(entry.getKey(), entry.getValue());
}
};
}
}
}