diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/CommonsClientAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/CommonsClientAutoConfiguration.java index 0f890f16..8b57f6eb 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/CommonsClientAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/CommonsClientAutoConfiguration.java @@ -23,7 +23,6 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -36,7 +35,7 @@ import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator; import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties; -import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthIndicator; +import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthContributor; import org.springframework.cloud.client.discovery.health.DiscoveryHealthIndicator; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.context.annotation.Bean; @@ -73,10 +72,10 @@ public class CommonsClientAutoConfiguration { @ConditionalOnProperty( value = "spring.cloud.discovery.client.composite-indicator.enabled", matchIfMissing = true) - @ConditionalOnBean({ DiscoveryHealthIndicator.class, HealthAggregator.class }) - public DiscoveryCompositeHealthIndicator discoveryCompositeHealthIndicator( - HealthAggregator aggregator, List indicators) { - return new DiscoveryCompositeHealthIndicator(aggregator, indicators); + @ConditionalOnBean({ DiscoveryHealthIndicator.class }) + public DiscoveryCompositeHealthContributor discoveryCompositeHealthContributor( + List indicators) { + return new DiscoveryCompositeHealthContributor(indicators); } @Bean diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthContributor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthContributor.java new file mode 100644 index 00000000..264427ca --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthContributor.java @@ -0,0 +1,80 @@ +/* + * 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.cloud.client.discovery.health; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +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.util.Assert; + +/** + * Adapter that converts a collection of {@link DiscoveryHealthIndicator} beans into a + * {@link CompositeHealthContributor}. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public class DiscoveryCompositeHealthContributor implements CompositeHealthContributor { + + private Map indicators; + + public DiscoveryCompositeHealthContributor( + Collection indicators) { + Assert.notNull(indicators, "'indicators' must not be null"); + this.indicators = indicators.stream().collect( + Collectors.toMap(DiscoveryHealthIndicator::getName, Function.identity())); + } + + @Override + public HealthContributor getContributor(String name) { + return asHealthIndicator(this.indicators.get(name)); + } + + @Override + public Iterator> iterator() { + return this.indicators.values().stream().map(this::asNamedContributor).iterator(); + } + + private NamedContributor asNamedContributor( + DiscoveryHealthIndicator indicator) { + return new NamedContributor() { + + @Override + public String getName() { + return indicator.getName(); + } + + @Override + public HealthIndicator getContributor() { + return asHealthIndicator(indicator); + } + + }; + } + + private HealthIndicator asHealthIndicator(DiscoveryHealthIndicator indicator) { + return (indicator != null) ? () -> indicator.health() : null; + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthIndicator.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthIndicator.java index d10ecbe6..2e14caff 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthIndicator.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthIndicator.java @@ -32,8 +32,10 @@ import org.springframework.boot.actuate.health.HealthIndicator; * and aggregates the statuses. * * @author Spencer Gibb + * @deprecated since 2.2.0 in favor of {@link DiscoveryCompositeHealthContributor} */ // TODO: do we need this? Can they just be independent HealthIndicators? +@Deprecated public class DiscoveryCompositeHealthIndicator extends CompositeHealthIndicator { private final ArrayList healthIndicators = new ArrayList<>(); diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/CommonsClientAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/CommonsClientAutoConfigurationTests.java index ef0c5bd9..cccd8283 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/CommonsClientAutoConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/CommonsClientAutoConfigurationTests.java @@ -25,7 +25,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.client.actuator.FeaturesEndpoint; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator; -import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthIndicator; +import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthContributor; import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; @@ -46,7 +46,7 @@ public class CommonsClientAutoConfigurationTests { public void beansCreatedNormally() { try (ConfigurableApplicationContext ctxt = init()) { then(ctxt.getBean(DiscoveryClientHealthIndicator.class)).isNotNull(); - then(ctxt.getBean(DiscoveryCompositeHealthIndicator.class)).isNotNull(); + then(ctxt.getBean(DiscoveryCompositeHealthContributor.class)).isNotNull(); then(ctxt.getBean(FeaturesEndpoint.class)).isNotNull(); then(ctxt.getBeansOfType(HasFeatures.class).values()).isNotEmpty(); } @@ -57,13 +57,9 @@ public class CommonsClientAutoConfigurationTests { try (ConfigurableApplicationContext ctxt = init( "spring.cloud.discovery.enabled=false")) { assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class); - assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class); - then(ctxt.getBean(FeaturesEndpoint.class)).isNotNull(); // features - // actuator - // is - // independent - // of - // discovery + assertBeanNonExistant(ctxt, DiscoveryCompositeHealthContributor.class); + then(ctxt.getBean(FeaturesEndpoint.class)).isNotNull(); + // features actuator is independent of discovery assertBeanNonExistant(ctxt, HasFeatures.class); } } @@ -75,7 +71,7 @@ public class CommonsClientAutoConfigurationTests { "spring.cloud.discovery.client.composite-indicator.enabled=false", "spring.cloud.features.enabled=false")) { assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class); - assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class); + assertBeanNonExistant(ctxt, DiscoveryCompositeHealthContributor.class); assertBeanNonExistant(ctxt, FeaturesEndpoint.class); } } @@ -85,7 +81,7 @@ public class CommonsClientAutoConfigurationTests { try (ConfigurableApplicationContext ctxt = init( "spring.cloud.discovery.client.health-indicator.enabled=false")) { assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class); - assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class); + assertBeanNonExistant(ctxt, DiscoveryCompositeHealthContributor.class); } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryClientHealthIndicatorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryClientHealthIndicatorTests.java index ea6e6db8..4b81bb30 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryClientHealthIndicatorTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryClientHealthIndicatorTests.java @@ -22,9 +22,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.CompositeHealthContributor; import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthAggregator; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; @@ -52,31 +53,37 @@ import static org.mockito.Mockito.mock; public class DiscoveryClientHealthIndicatorTests { @Autowired - private DiscoveryCompositeHealthIndicator healthIndicator; + private DiscoveryCompositeHealthContributor healthContributor; @Autowired private DiscoveryClientHealthIndicator clientHealthIndicator; @Test public void testHealthIndicatorDescriptionDisabled() { - then(this.healthIndicator).as("healthIndicator was null").isNotNull(); - Health health = this.healthIndicator.health(); - assertHealth(health, Status.UNKNOWN); + then(this.healthContributor).as("healthIndicator was null").isNotNull(); + assertHealth(getHealth("testDiscoveryHealthIndicator"), Status.UNKNOWN); + assertHealth(getHealth("discoveryClient"), Status.UNKNOWN); this.clientHealthIndicator .onApplicationEvent(new InstanceRegisteredEvent<>(this, null)); - health = this.healthIndicator.health(); - Status status = assertHealth(health, Status.UP); + assertHealth(getHealth("testDiscoveryHealthIndicator"), Status.UNKNOWN); + Status status = assertHealth(getHealth("discoveryClient"), Status.UP); then(status.getDescription()).as("status description was wrong") .isEqualTo("TestDiscoveryClient"); } + private Health getHealth(String name) { + HealthContributor delegate = ((CompositeHealthContributor) this.healthContributor) + .getContributor(name); + return ((HealthIndicator) delegate).health(); + } + private Status assertHealth(Health health, Status expected) { then(health).as("health was null").isNotNull(); Status status = health.getStatus(); then(status).as("status was null").isNotNull(); - then(expected.getCode()).isEqualTo(status.getCode()).as("status code was wrong"); + then(status.getCode()).isEqualTo(expected.getCode()).as("status code was wrong"); return status; } @@ -84,11 +91,6 @@ public class DiscoveryClientHealthIndicatorTests { @EnableConfigurationProperties public static class Config { - @Bean - public HealthAggregator healthAggregator() { - return new OrderedHealthAggregator(); - } - @Bean public DiscoveryClient discoveryClient() { DiscoveryClient mock = mock(DiscoveryClient.class); @@ -100,6 +102,7 @@ public class DiscoveryClientHealthIndicatorTests { @Bean public DiscoveryHealthIndicator discoveryHealthIndicator() { return new DiscoveryHealthIndicator() { + @Override public String getName() { return "testDiscoveryHealthIndicator"; @@ -109,6 +112,7 @@ public class DiscoveryClientHealthIndicatorTests { public Health health() { return new Health.Builder().unknown().build(); } + }; } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthContributorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthContributorTests.java new file mode 100644 index 00000000..3ab670df --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthContributorTests.java @@ -0,0 +1,111 @@ +/* + * 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.cloud.client.discovery.health; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +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 static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link DiscoveryCompositeHealthContributor}. + * + * @author Phillip Webb + */ +public class DiscoveryCompositeHealthContributorTests { + + @Test + public void createWhenIndicatorsAreNullThrowsException() throws Exception { + assertThatIllegalArgumentException() + .isThrownBy(() -> new DiscoveryCompositeHealthContributor(null)) + .withMessage("'indicators' must not be null"); + } + + @Test + public void getContributorReturnsContributor() throws Exception { + TestDiscoveryHealthIndicator indicator = new TestDiscoveryHealthIndicator("test", + Health.up().build()); + DiscoveryCompositeHealthContributor composite = new DiscoveryCompositeHealthContributor( + Arrays.asList(indicator)); + HealthIndicator adapted = (HealthIndicator) composite.getContributor("test"); + assertThat(adapted).isNotNull(); + assertThat(adapted.health()).isSameAs(indicator.health()); + } + + @Test + public void getContributorWhenMissingReturnsNull() throws Exception { + TestDiscoveryHealthIndicator indicator = new TestDiscoveryHealthIndicator("test", + Health.up().build()); + DiscoveryCompositeHealthContributor composite = new DiscoveryCompositeHealthContributor( + Arrays.asList(indicator)); + assertThat((HealthIndicator) composite.getContributor("missing")).isNull(); + } + + @Test + public void iteratorIteratesNamedContributors() throws Exception { + TestDiscoveryHealthIndicator indicator1 = new TestDiscoveryHealthIndicator( + "test1", Health.up().build()); + TestDiscoveryHealthIndicator indicator2 = new TestDiscoveryHealthIndicator( + "test2", Health.down().build()); + DiscoveryCompositeHealthContributor composite = new DiscoveryCompositeHealthContributor( + Arrays.asList(indicator1, indicator2)); + List> contributors = new ArrayList<>(); + for (NamedContributor contributor : composite) { + contributors.add(contributor); + } + assertThat(contributors).hasSize(2); + assertThat(contributors).extracting("name").containsExactlyInAnyOrder("test1", + "test2"); + assertThat(contributors).extracting("contributor").extracting("health") + .containsExactlyInAnyOrder(indicator1.health(), indicator2.health()); + } + + private static class TestDiscoveryHealthIndicator + implements DiscoveryHealthIndicator { + + private final String name; + + private final Health health; + + TestDiscoveryHealthIndicator(String name, Health health) { + super(); + this.name = name; + this.health = health; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Health health() { + return this.health; + } + + } + +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthIndicatorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthIndicatorTests.java deleted file mode 100644 index b3aa10be..00000000 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/DiscoveryCompositeHealthIndicatorTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.cloud.client.discovery.health; - -import java.util.Arrays; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthAggregator; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.client.CommonsClientAutoConfiguration; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.junit4.SpringRunner; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * @author Spencer Gibb - */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = { DiscoveryCompositeHealthIndicatorTests.Config.class, - CommonsClientAutoConfiguration.class }) -public class DiscoveryCompositeHealthIndicatorTests { - - @Autowired - private DiscoveryCompositeHealthIndicator healthIndicator; - - @Autowired - private DiscoveryClientHealthIndicator clientHealthIndicator; - - @Test - public void testHealthIndicator() { - then(this.healthIndicator).as("healthIndicator was null").isNotNull(); - Health health = this.healthIndicator.health(); - assertHealth(health, Status.UNKNOWN); - - this.clientHealthIndicator - .onApplicationEvent(new InstanceRegisteredEvent<>(this, null)); - - health = this.healthIndicator.health(); - Status status = assertHealth(health, Status.UP); - then("").isEqualTo(status.getDescription()).as("status description was wrong"); - } - - protected Status assertHealth(Health health, Status expected) { - then(health).as("health was null").isNotNull(); - Status status = health.getStatus(); - then(status).as("status was null").isNotNull(); - then(expected.getCode()).isEqualTo(status.getCode()).as("status code was wrong"); - return status; - } - - @Configuration - public static class Config { - - @Bean - public HealthAggregator healthAggregator() { - return new OrderedHealthAggregator(); - } - - @Bean - public DiscoveryClient discoveryClient() { - DiscoveryClient mock = mock(DiscoveryClient.class); - given(mock.description()).willReturn("TestDiscoveryClient"); - given(mock.getServices()).willReturn(Arrays.asList("TestService1")); - return mock; - } - - @Bean - public DiscoveryHealthIndicator discoveryHealthIndicator() { - return new DiscoveryHealthIndicator() { - @Override - public String getName() { - return "testDiscoveryHealthIndicator"; - } - - @Override - public Health health() { - return new Health.Builder().unknown().build(); - } - }; - } - - } - -}