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