From 7ea84022687c38d18749504c5327fb82a1003bd9 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Sat, 16 Jan 2016 01:36:46 +0100 Subject: [PATCH] Introduce HealthIndicatorRegistry This commit introduces HealthIndicatorRegistry which handles registration and invocation of HealthIndicator instances. Registering new HealthIndicator instances is now possible in runtime. The default implementation offers the option to run the registered health indicators in parallel. --- .../EndpointAutoConfiguration.java | 11 +- .../HealthIndicatorAutoConfiguration.java | 78 ++++++++- .../boot/actuate/endpoint/HealthEndpoint.java | 38 ++--- .../DefaultHealthIndicatorRegistry.java | 149 ++++++++++++++++++ ...aultHealthIndicatorRegistryProperties.java | 55 +++++++ .../health/HealthIndicatorRegistry.java | 61 +++++++ ...althMvcEndpointAutoConfigurationTests.java | 12 +- .../actuate/endpoint/HealthEndpointTests.java | 17 +- .../DefaultHealthIndicatorRegistryTest.java | 96 +++++++++++ .../appendix-application-properties.adoc | 2 + .../asciidoc/production-ready-features.adoc | 10 +- 11 files changed, 489 insertions(+), 40 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryProperties.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index 919586a2ab00..6c3268f445d6 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -46,8 +45,9 @@ import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; +import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry; 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.OrderedHealthAggregator; import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.TraceRepository; @@ -81,6 +81,7 @@ * @author Christian Dupuis * @author Stephane Nicoll * @author EddĂș MelĂ©ndez + * @author Vedran Pavic */ @Configuration @AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class }) @@ -94,7 +95,7 @@ public class EndpointAutoConfiguration { private HealthAggregator healthAggregator = new OrderedHealthAggregator(); @Autowired(required = false) - private Map healthIndicators = new HashMap(); + private HealthIndicatorRegistry healthIndicatorRegistry = new DefaultHealthIndicatorRegistry(); @Autowired(required = false) private Collection publicMetrics; @@ -111,7 +112,7 @@ public EnvironmentEndpoint environmentEndpoint() { @Bean @ConditionalOnMissingBean public HealthEndpoint healthEndpoint() { - return new HealthEndpoint(this.healthAggregator, this.healthIndicators); + return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry); } @Bean diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index c843105d726c..4eb825359155 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -27,18 +27,23 @@ import org.elasticsearch.client.Client; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.CassandraHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator; +import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry; +import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistryProperties; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties; import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties; 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.JmsHealthIndicator; import org.springframework.boot.actuate.health.MailHealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator; @@ -84,6 +89,7 @@ * @author Stephane Nicoll * @author Phillip Webb * @author Tommy Ludwig + * @author Vedran Pavic * @since 1.1.0 */ @Configuration @@ -100,6 +106,14 @@ public class HealthIndicatorAutoConfiguration { @Autowired private HealthIndicatorAutoConfigurationProperties configurationProperties = new HealthIndicatorAutoConfigurationProperties(); + @Autowired + private HealthIndicatorRegistry healthIndicatorRegistry; + + @Bean + public HealthIndicatorRegistryPostProcessor healthIndicatorRegistryPostProcessor() { + return new HealthIndicatorRegistryPostProcessor(this.healthIndicatorRegistry); + } + @Bean @ConditionalOnMissingBean(HealthAggregator.class) public OrderedHealthAggregator healthAggregator() { @@ -116,6 +130,21 @@ public ApplicationHealthIndicator applicationHealthIndicator() { return new ApplicationHealthIndicator(); } + @Configuration + @ConditionalOnMissingBean(HealthIndicatorRegistry.class) + @EnableConfigurationProperties(DefaultHealthIndicatorRegistryProperties.class) + public static class HealthIndicatorRegistryAutoConfiguration { + + @Autowired + private DefaultHealthIndicatorRegistryProperties properties; + + @Bean + public HealthIndicatorRegistry healthIndicatorRegistry() { + return new DefaultHealthIndicatorRegistry(this.properties); + } + + } + /** * Base class for configurations that can combine source beans using a * {@link CompositeHealthIndicator}. @@ -363,4 +392,51 @@ protected ElasticsearchHealthIndicator createHealthIndicator(Client client) { } + /** + * A {@link BeanPostProcessor} that registers {@link HealthIndicator} beans with a + * {@link HealthIndicatorRegistry}. + */ + private static class HealthIndicatorRegistryPostProcessor implements BeanPostProcessor { + + private HealthIndicatorRegistry healthIndicatorRegistry; + + HealthIndicatorRegistryPostProcessor(HealthIndicatorRegistry healthIndicatorRegistry) { + this.healthIndicatorRegistry = healthIndicatorRegistry; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof HealthIndicator) { + postProcessHealthIndicator((HealthIndicator) bean, beanName); + } + return bean; + } + + private void postProcessHealthIndicator( + HealthIndicator healthIndicator, String beanName) { + this.healthIndicatorRegistry.register(getKey(beanName), healthIndicator); + } + + /** + * Turns the bean name into a key that can be used in the map of health information. + * @param name the bean name + * @return the key + */ + private String getKey(String name) { + int index = name.toLowerCase().indexOf("healthindicator"); + if (index > 0) { + return name.substring(0, index); + } + return name; + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java index 7e171bab6801..5c14de9c59e3 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -18,10 +18,10 @@ import java.util.Map; -import org.springframework.boot.actuate.health.CompositeHealthIndicator; 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.HealthIndicatorRegistry; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; @@ -31,11 +31,13 @@ * @author Dave Syer * @author Christian Dupuis * @author Andy Wilkinson + * @author Vedran Pavic */ @ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = true) public class HealthEndpoint extends AbstractEndpoint { - private final HealthIndicator healthIndicator; + private final HealthAggregator healthAggregator; + private final HealthIndicatorRegistry healthIndicatorRegistry; /** * Time to live for cached result, in milliseconds. @@ -45,19 +47,16 @@ public class HealthEndpoint extends AbstractEndpoint { /** * Create a new {@link HealthIndicator} instance. * @param healthAggregator the health aggregator - * @param healthIndicators the health indicators + * @param healthIndicatorRegistry the health indicator registry */ public HealthEndpoint(HealthAggregator healthAggregator, - Map healthIndicators) { + HealthIndicatorRegistry healthIndicatorRegistry) { super("health", false); Assert.notNull(healthAggregator, "HealthAggregator must not be null"); - Assert.notNull(healthIndicators, "HealthIndicators must not be null"); - CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator( - healthAggregator); - for (Map.Entry entry : healthIndicators.entrySet()) { - healthIndicator.addHealthIndicator(getKey(entry.getKey()), entry.getValue()); - } - this.healthIndicator = healthIndicator; + Assert.notNull(healthIndicatorRegistry, + "healthIndicatorRegistry must not be null"); + this.healthAggregator = healthAggregator; + this.healthIndicatorRegistry = healthIndicatorRegistry; } /** @@ -78,19 +77,8 @@ public void setTimeToLive(long ttl) { */ @Override public Health invoke() { - return this.healthIndicator.health(); + Map healths = this.healthIndicatorRegistry.runHealthIndicators(); + return this.healthAggregator.aggregate(healths); } - /** - * Turns the bean name into a key that can be used in the map of health information. - * @param name the bean name - * @return the key - */ - private String getKey(String name) { - int index = name.toLowerCase().indexOf("healthindicator"); - if (index > 0) { - return name.substring(0, index); - } - return name; - } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java new file mode 100644 index 000000000000..0020029aec3f --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java @@ -0,0 +1,149 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Default implementation of {@link HealthIndicatorRegistry}. + * + * @author Vedran Pavic + */ +public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry { + + private static final Log logger = LogFactory.getLog(DefaultHealthIndicatorRegistry.class); + + private final Map healthIndicators = + new HashMap(); + + private DefaultHealthIndicatorRegistryProperties properties; + + private ExecutorService executor; + + /** + * Create a {@link DefaultHealthIndicatorRegistry} instance. + * @param properties the configuration properties + */ + public DefaultHealthIndicatorRegistry(DefaultHealthIndicatorRegistryProperties properties) { + Assert.notNull(properties, "Properties must not be null"); + this.properties = properties; + if (this.properties.isRunInParallel()) { + this.executor = Executors.newFixedThreadPool( + this.properties.getThreadCount(), new WorkerThreadFactory()); + } + } + + /** + * Create a {@link DefaultHealthIndicatorRegistry} instance. + */ + public DefaultHealthIndicatorRegistry() { + this(new DefaultHealthIndicatorRegistryProperties()); + } + + @Override + public void register(String name, HealthIndicator healthIndicator) { + synchronized (this.healthIndicators) { + this.healthIndicators.put(name, healthIndicator); + } + } + + @Override + public void unregister(String name) { + synchronized (this.healthIndicators) { + this.healthIndicators.remove(name); + } + } + + @Override + public Set getRegisteredNames() { + return Collections.unmodifiableSet(new HashSet(this.healthIndicators.keySet())); + } + + @Override + public Health runHealthIndicator(String name) { + HealthIndicator healthIndicator = this.healthIndicators.get(name); + Assert.notNull(healthIndicator, "HealthIndicator " + name + " does not exist"); + return healthIndicator.health(); + } + + @Override + public Map runHealthIndicators() { + Map healths = new HashMap(); + if (this.properties.isRunInParallel()) { + Assert.notNull(this.executor, "Executor must not be null"); + Map> futures = new HashMap>(); + for (final Map.Entry entry : this.healthIndicators.entrySet()) { + Future future = this.executor.submit(new Callable() { + @Override + public Health call() throws Exception { + return entry.getValue().health(); + } + }); + futures.put(entry.getKey(), future); + } + for (Map.Entry> entry : futures.entrySet()) { + try { + healths.put(entry.getKey(), entry.getValue().get()); + } + catch (Exception e) { + logger.warn("Error invoking health indicator '" + entry.getKey() + "'", e); + healths.put(entry.getKey(), Health.down(e).build()); + } + } + } + else { + for (Map.Entry entry : this.healthIndicators.entrySet()) { + healths.put(entry.getKey(), entry.getValue().health()); + } + } + return healths; + } + + /** + * {@link ThreadFactory} to create the worker threads. + */ + private static class WorkerThreadFactory implements ThreadFactory { + + private final AtomicInteger threadNumber = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName(ClassUtils.getShortName(getClass()) + + "-" + this.threadNumber.getAndIncrement()); + return thread; + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryProperties.java new file mode 100644 index 000000000000..1d9a6b9a5fcb --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryProperties.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * External configuration properties for {@link DefaultHealthIndicatorRegistry}. + * + * @author Vedran Pavic + */ +@ConfigurationProperties("management.health.registry") +public class DefaultHealthIndicatorRegistryProperties { + + /** + * Flag to indicate the health indicators should run in parallel. + */ + private boolean runInParallel = false; + + /** + * Number of threads used to run health indicators in parallel. + */ + private int threadCount = 2; + + public boolean isRunInParallel() { + return this.runInParallel; + } + + public void setRunInParallel(boolean runInParallel) { + this.runInParallel = runInParallel; + } + + public int getThreadCount() { + return this.threadCount; + } + + public void setThreadCount(int threadCount) { + this.threadCount = threadCount; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java new file mode 100644 index 000000000000..cb4c310f22e8 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2016 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 + * + * http://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; +import java.util.Set; + +/** + * A registry of {@link HealthIndicator} instances. + * + * @author Vedran Pavic + */ +public interface HealthIndicatorRegistry { + + /** + * Register the health indicator with given name. + * @param name the health indicator name + * @param healthIndicator the health indicator to register + */ + void register(String name, HealthIndicator healthIndicator); + + /** + * Unregister the health indicator with given name. + * @param name the health indicator name + */ + void unregister(String name); + + /** + * Return names of all currently registered health indicators. + * @return the health indicator names + */ + Set getRegisteredNames(); + + /** + * Return an indication of health for given health indicator name. + * @param name the health indicator name + * @return the health + */ + Health runHealthIndicator(String name); + + /** + * Return a map of healths for all registered health indicators. + * @return the map of healths + */ + Map runHealthIndicators(); + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java index 0c00865d8626..942b9c4f4efb 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthMvcEndpointAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -21,8 +21,10 @@ import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.Status; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; @@ -42,6 +44,7 @@ * * @author Dave Syer * @author Andy Wilkinson + * @author Vedran Pavic */ public class HealthMvcEndpointAutoConfigurationTests { @@ -94,6 +97,13 @@ public TestHealthIndicator testHealthIndicator() { return new TestHealthIndicator(); } + @Bean + public HealthIndicatorRegistry healthIndicatorRegistry() { + DefaultHealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); + registry.register("test", testHealthIndicator()); + return registry; + } + } static class TestHealthIndicator extends AbstractHealthIndicator { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java index 3087538780e7..e6703a0bb38c 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -16,13 +16,13 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Map; - import org.junit.Test; +import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry; 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.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.Status; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -37,6 +37,7 @@ * * @author Phillip Webb * @author Christian Dupuis + * @author Vedran Pavic */ public class HealthEndpointTests extends AbstractEndpointTests { @@ -56,8 +57,8 @@ public static class Config { @Bean public HealthEndpoint endpoint(HealthAggregator healthAggregator, - Map healthIndicators) { - return new HealthEndpoint(healthAggregator, healthIndicators); + HealthIndicatorRegistry healthIndicatorRegistry) { + return new HealthEndpoint(healthAggregator, healthIndicatorRegistry); } @Bean @@ -75,5 +76,11 @@ public Health health() { public HealthAggregator healthAggregator() { return new OrderedHealthAggregator(); } + + @Bean + public HealthIndicatorRegistry healthIndicatorRegistry() { + return new DefaultHealthIndicatorRegistry(); + } + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java new file mode 100644 index 000000000000..9cfa18c36fa8 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2016 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 + * + * http://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; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Tests for {@link DefaultHealthIndicatorRegistry}. + * + * @author Vedran Pavic + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultHealthIndicatorRegistryTest { + + @Mock + private HealthIndicator one; + + @Mock + private HealthIndicator two; + + private DefaultHealthIndicatorRegistry registry; + + private DefaultHealthIndicatorRegistryProperties properties = new DefaultHealthIndicatorRegistryProperties(); + + @Before + public void setup() { + given(this.one.health()).willReturn(new Health.Builder().up().build()); + given(this.two.health()).willReturn(new Health.Builder().unknown().build()); + + this.registry = new DefaultHealthIndicatorRegistry(this.properties); + } + + @Test + public void createAndRunAll() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + Map healths = this.registry.runHealthIndicators(); + assertThat(healths.size(), equalTo(2)); + assertThat(healths.get("one").getStatus(), equalTo(Status.UP)); + assertThat(healths.get("two").getStatus(), equalTo(Status.UNKNOWN)); + } + + @Test + public void createAndRunAllInParallel() { + this.properties.setRunInParallel(true); + this.registry.register("one", this.one); + this.registry.register("two", this.two); + Map healths = this.registry.runHealthIndicators(); + assertThat(healths.size(), equalTo(2)); + assertThat(healths.get("one").getStatus(), equalTo(Status.UP)); + assertThat(healths.get("two").getStatus(), equalTo(Status.UNKNOWN)); + } + + @Test + public void createAndRunSingle() { + this.registry.register("one", this.one); + Health health = this.registry.runHealthIndicator("one"); + assertThat(health.getStatus(), equalTo(Status.UP)); + } + + @Test + public void testUnregister() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + Map healths = this.registry.runHealthIndicators(); + assertThat(healths.size(), equalTo(2)); + this.registry.unregister("two"); + healths = this.registry.runHealthIndicators(); + assertThat(healths.size(), equalTo(1)); + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 601cee365e56..4f56da64dfd8 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -831,6 +831,8 @@ content into your application; rather pick only the properties that you need. management.health.mongo.enabled=true # Enable MongoDB health check. management.health.rabbit.enabled=true # Enable RabbitMQ health check. management.health.redis.enabled=true # Enable Redis health check. + management.health.registry.run-in-parallel=false # Flag to indicate the health indicators should run in parallel. + management.health.registry.thread-count=2 # Number of threads used to run health indicators in parallel. management.health.solr.enabled=true # Enable Solr health check. management.health.status.order=DOWN, OUT_OF_SERVICE, UNKNOWN, UP # Comma-separated list of health statuses in order of severity. diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index c70007b86559..987f5c712017 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -255,9 +255,10 @@ returned, and for an authenticated connection additional details are also displa <> for HTTP details). Health information is collected from all -{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] beans defined -in your `ApplicationContext`. Spring Boot includes a number of auto-configured -`HealthIndicators` and you can also write your own. +{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] instances +registered with {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[`HealthIndicatorRegistry`]. +Spring Boot includes a number of auto-configured `HealthIndicators` and you can also write +your own. @@ -347,6 +348,9 @@ NOTE: The identifier for a given `HealthIndicator` is the name of the bean witho `HealthIndicator` suffix if it exists. In the example above, the health information will be available in an entry named `my`. +Additionally, you can register (and unregister) `HealthIndicator` instances in runtime +using {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[`HealthIndicatorRegistry`]. + In addition to Spring Boot's predefined {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for `Health` to return a custom `Status` that represents a new system state. In such cases a custom implementation of the