diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java index b4cb08798..0e5a8d00c 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java @@ -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 healthIndicators = new HashMap<>(); + private Map healthContributors = new HashMap<>(); /** * {@code true} until the context is stopped. */ private boolean running = true; - private Map reactiveHealthIndicators = new HashMap<>(); + private Map reactiveHealthContributors = new HashMap<>(); public EurekaHealthCheckHandler(StatusAggregator statusAggregator) { this.statusAggregator = statusAggregator; @@ -99,39 +102,28 @@ public class EurekaHealthCheckHandler @Override public void afterPropertiesSet() { - final Map healthIndicators = applicationContext.getBeansOfType(HealthIndicator.class); - final Map reactiveHealthIndicators = applicationContext - .getBeansOfType(ReactiveHealthIndicator.class); - - populateHealthIndicators(healthIndicators); - populateReactiveHealthIndicators(reactiveHealthIndicators); + populateHealthContributors(applicationContext.getBeansOfType(HealthContributor.class)); + reactiveHealthContributors.putAll(applicationContext.getBeansOfType(ReactiveHealthContributor.class)); } - void populateHealthIndicators(Map healthIndicators) { - for (Map.Entry entry : healthIndicators.entrySet()) { + void populateHealthContributors(Map healthContributors) { + for (Map.Entry 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 reactiveHealthIndicators) { - for (Map.Entry 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 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 statusSet, HealthContributor contributor) { + if (contributor instanceof CompositeHealthContributor) { + for (NamedContributor contrib : (CompositeHealthContributor) contributor) { + processContributor(statusSet, contrib.getContributor()); + } + } + else if (contributor instanceof HealthIndicator) { + statusSet.add(((HealthIndicator) contributor).health().getStatus()); + } + } + + private void processContributor(Set statusSet, ReactiveHealthContributor contributor) { + if (contributor instanceof CompositeReactiveHealthContributor) { + for (NamedContributor 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) { diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java index 1a30dfaac..5448bb49c 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java @@ -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 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> iterator() { + Iterator> iterator = contributorMap.entrySet().iterator(); + return new Iterator>() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public NamedContributor next() { + Map.Entry entry = iterator.next(); + return NamedContributor.of(entry.getKey(), entry.getValue()); + } + + }; + } + + } + }