diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 90217db5ca..f5c4256512 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -303,6 +303,11 @@ reactor-netty test + + io.projectreactor + reactor-test + test + io.undertow undertow-core @@ -369,5 +374,10 @@ snakeyaml test + + redis.clients + jedis + true + diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java index b00b90da6b..f40f0d5b5c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java @@ -17,8 +17,10 @@ package org.springframework.boot.actuate.autoconfigure.endpoint; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import liquibase.integration.spring.SpringLiquibase; import org.flywaydb.core.Flyway; @@ -42,10 +44,11 @@ import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.StatusEndpoint; import org.springframework.boot.actuate.endpoint.ThreadDumpEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; +import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorFactory; import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.TraceRepository; @@ -55,6 +58,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionEvaluationRepor import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; @@ -62,8 +66,10 @@ import org.springframework.boot.endpoint.Endpoint; import org.springframework.boot.logging.LoggingSystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.Environment; +import org.springframework.util.ObjectUtils; import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; /** @@ -174,15 +180,16 @@ public class EndpointAutoConfiguration { } @Configuration + @Import(HealthIndicatorsSupplierConfiguration.class) static class HealthEndpointConfiguration { private final HealthIndicator healthIndicator; HealthEndpointConfiguration(ObjectProvider healthAggregator, - ObjectProvider> healthIndicators) { - this.healthIndicator = new HealthIndicatorFactory().createHealthIndicator( + Supplier> healthIndicatorsSupplier) { + this.healthIndicator = new CompositeHealthIndicatorFactory().createHealthIndicator( healthAggregator.getIfAvailable(OrderedHealthAggregator::new), - healthIndicators.getIfAvailable(Collections::emptyMap)); + healthIndicatorsSupplier.get()); } @Bean @@ -201,6 +208,55 @@ public class EndpointAutoConfiguration { } + @Configuration + static class HealthIndicatorsSupplierConfiguration { + + @Configuration + @ConditionalOnMissingClass("reactor.core.publisher.Flux") + static class SimpleHealthIndicatorsSupplierConfiguration { + + @Bean + public Supplier> allHealthIndicators( + ObjectProvider> healthIndicators) { + return () -> healthIndicators.getIfAvailable(Collections::emptyMap); + } + + } + + @Configuration + @ConditionalOnClass(name = "reactor.core.publisher.Flux") + static class ReactiveHealthIndicatorsSupplierConfiguration { + + @Bean + public Supplier> allHealthIndicators( + ObjectProvider> healthIndicators, + ObjectProvider> reactiveHealthIndicators) { + return () -> merge(healthIndicators.getIfAvailable(Collections::emptyMap), + reactiveHealthIndicators.getIfAvailable(Collections::emptyMap)); + } + + private Map merge( + Map healthIndicators, + Map reactiveHealthIndicators) { + if (ObjectUtils.isEmpty(reactiveHealthIndicators)) { + return healthIndicators; + } + Map allIndicators = new LinkedHashMap<>( + healthIndicators); + reactiveHealthIndicators.forEach((beanName, indicator) -> { + allIndicators.computeIfAbsent(beanName, n -> adapt(indicator)); + }); + return allIndicators; + } + + private HealthIndicator adapt(ReactiveHealthIndicator healthIndicator) { + return () -> healthIndicator.health().block(); + } + + } + + } + @Configuration @ConditionalOnBean(Flyway.class) @ConditionalOnClass(Flyway.class) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointConfiguration.java new file mode 100644 index 0000000000..91c8b48e41 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/HealthWebEndpointConfiguration.java @@ -0,0 +1,129 @@ +/* + * Copyright 2012-2017 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.autoconfigure.endpoint.web; + +import java.util.Collections; +import java.util.Map; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorProperties; +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.endpoint.StatusEndpoint; +import org.springframework.boot.actuate.endpoint.web.HealthReactiveWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.StatusReactiveWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.StatusWebEndpointExtension; +import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicatorFactory; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration for web-specific health endpoints + * . + * @author Stephane Nicoll + * @since 2.0.0 + */ +@Configuration +@EnableConfigurationProperties(HealthIndicatorProperties.class) +public class HealthWebEndpointConfiguration { + + @Bean + @ConditionalOnMissingBean + public HealthStatusHttpMapper createHealthStatusHttpMapper( + HealthIndicatorProperties healthIndicatorProperties) { + HealthStatusHttpMapper statusHttpMapper = new HealthStatusHttpMapper(); + if (healthIndicatorProperties.getHttpMapping() != null) { + statusHttpMapper.addStatusMapping(healthIndicatorProperties.getHttpMapping()); + } + return statusHttpMapper; + } + + @Configuration + @ConditionalOnWebApplication(type = Type.REACTIVE) + static class ReactiveWebHealthConfiguration { + + private final ReactiveHealthIndicator reactiveHealthIndicator; + + ReactiveWebHealthConfiguration(ObjectProvider healthAggregator, + ObjectProvider> reactiveHealthIndicators, + ObjectProvider> healthIndicators) { + this.reactiveHealthIndicator = new CompositeReactiveHealthIndicatorFactory().createReactiveHealthIndicator( + healthAggregator.getIfAvailable(OrderedHealthAggregator::new), + reactiveHealthIndicators.getIfAvailable(Collections::emptyMap), + healthIndicators.getIfAvailable(Collections::emptyMap)); + } + + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(value = HealthEndpoint.class, search = SearchStrategy.CURRENT) + public HealthReactiveWebEndpointExtension healthWebEndpointExtension( + HealthStatusHttpMapper healthStatusHttpMapper) { + return new HealthReactiveWebEndpointExtension(this.reactiveHealthIndicator, + healthStatusHttpMapper); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(value = StatusEndpoint.class, search = SearchStrategy.CURRENT) + public StatusReactiveWebEndpointExtension statusWebEndpointExtension( + HealthStatusHttpMapper healthStatusHttpMapper) { + return new StatusReactiveWebEndpointExtension(this.reactiveHealthIndicator, + healthStatusHttpMapper); + } + + } + + @Configuration + @ConditionalOnWebApplication(type = Type.SERVLET) + static class ServletWebHealthConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(value = HealthEndpoint.class, search = SearchStrategy.CURRENT) + public HealthWebEndpointExtension healthWebEndpointExtension( + HealthEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) { + return new HealthWebEndpointExtension(delegate, healthStatusHttpMapper); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(value = StatusEndpoint.class, search = SearchStrategy.CURRENT) + public StatusWebEndpointExtension statusWebEndpointExtension( + StatusEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) { + return new StatusWebEndpointExtension(delegate, healthStatusHttpMapper); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java index 2c1676c91a..afb3ee102e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfiguration.java @@ -18,26 +18,20 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorProperties; import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; -import org.springframework.boot.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.actuate.endpoint.StatusEndpoint; import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension; -import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint; import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint; -import org.springframework.boot.actuate.endpoint.web.StatusWebEndpointExtension; -import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.StringUtils; @@ -49,7 +43,7 @@ import org.springframework.util.StringUtils; * @since 2.0.0 */ @ManagementContextConfiguration -@EnableConfigurationProperties(HealthIndicatorProperties.class) +@Import(HealthWebEndpointConfiguration.class) public class WebEndpointManagementContextConfiguration { @Bean @@ -59,35 +53,6 @@ public class WebEndpointManagementContextConfiguration { return new HeapDumpWebEndpoint(); } - @Bean - @ConditionalOnMissingBean - @ConditionalOnEnabledEndpoint - @ConditionalOnBean(value = HealthEndpoint.class, search = SearchStrategy.CURRENT) - public HealthWebEndpointExtension healthWebEndpointExtension(HealthEndpoint delegate, - HealthStatusHttpMapper healthStatusHttpMapper) { - return new HealthWebEndpointExtension(delegate, healthStatusHttpMapper); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnEnabledEndpoint - @ConditionalOnBean(value = StatusEndpoint.class, search = SearchStrategy.CURRENT) - public StatusWebEndpointExtension statusWebEndpointExtension(StatusEndpoint delegate, - HealthStatusHttpMapper healthStatusHttpMapper) { - return new StatusWebEndpointExtension(delegate, healthStatusHttpMapper); - } - - @Bean - @ConditionalOnMissingBean - public HealthStatusHttpMapper createHealthStatusHttpMapper( - HealthIndicatorProperties healthIndicatorProperties) { - HealthStatusHttpMapper statusHttpMapper = new HealthStatusHttpMapper(); - if (healthIndicatorProperties.getHttpMapping() != null) { - statusHttpMapper.addStatusMapping(healthIndicatorProperties.getHttpMapping()); - } - return statusHttpMapper; - } - @Bean @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java new file mode 100644 index 0000000000..37926bb682 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2017 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.autoconfigure.health; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicator; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.core.ResolvableType; + +/** + * Reactive variant of {@link CompositeHealthIndicatorConfiguration}. + * + * @param the health indicator type + * @param the bean source type + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class CompositeReactiveHealthIndicatorConfiguration { + + @Autowired + private HealthAggregator healthAggregator; + + protected ReactiveHealthIndicator createHealthIndicator(Map beans) { + if (beans.size() == 1) { + return createHealthIndicator(beans.values().iterator().next()); + } + CompositeReactiveHealthIndicator composite = new CompositeReactiveHealthIndicator( + this.healthAggregator); + for (Map.Entry entry : beans.entrySet()) { + composite.addHealthIndicator(entry.getKey(), + createHealthIndicator(entry.getValue())); + } + return composite; + } + + @SuppressWarnings("unchecked") + protected H createHealthIndicator(S source) { + Class[] generics = ResolvableType + .forClass(CompositeReactiveHealthIndicatorConfiguration.class, getClass()) + .resolveGenerics(); + Class indicatorClass = (Class) generics[0]; + Class sourceClass = (Class) generics[1]; + try { + return indicatorClass.getConstructor(sourceClass).newInstance(source); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to create indicator " + indicatorClass + + " for source " + sourceClass, ex); + } + } + +} + diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java index d0c976610c..89aa71f638 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java @@ -16,46 +16,15 @@ package org.springframework.boot.actuate.autoconfigure.health; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.jms.ConnectionFactory; -import javax.sql.DataSource; - -import com.couchbase.client.java.Bucket; -import com.datastax.driver.core.Cluster; -import org.apache.solr.client.solrj.SolrClient; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.health.ApplicationHealthIndicator; -import org.springframework.boot.actuate.health.CassandraHealthIndicator; -import org.springframework.boot.actuate.health.CouchbaseHealthIndicator; -import org.springframework.boot.actuate.health.DataSourceHealthIndicator; -import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; -import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.JmsHealthIndicator; -import org.springframework.boot.actuate.health.LdapHealthIndicator; -import org.springframework.boot.actuate.health.MailHealthIndicator; -import org.springframework.boot.actuate.health.MongoHealthIndicator; -import org.springframework.boot.actuate.health.Neo4jHealthIndicator; import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.boot.actuate.health.RabbitHealthIndicator; -import org.springframework.boot.actuate.health.RedisHealthIndicator; -import org.springframework.boot.actuate.health.SolrHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration; @@ -66,9 +35,6 @@ import org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfigurat import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadata; -import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvider; -import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProviders; import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration; import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; @@ -78,14 +44,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.data.cassandra.core.CassandraOperations; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; -import org.springframework.ldap.core.LdapOperations; -import org.springframework.mail.javamail.JavaMailSenderImpl; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s. @@ -110,9 +68,8 @@ import org.springframework.mail.javamail.JavaMailSenderImpl; Neo4jDataAutoConfiguration.class, RabbitAutoConfiguration.class, RedisAutoConfiguration.class, SolrAutoConfiguration.class }) @EnableConfigurationProperties({ HealthIndicatorProperties.class }) -@Import({ - ElasticsearchHealthIndicatorConfiguration.ElasticsearchClientHealthIndicatorConfiguration.class, - ElasticsearchHealthIndicatorConfiguration.ElasticsearchJestHealthIndicatorConfiguration.class }) +@Import({ ReactiveHealthIndicatorsConfiguration.class, + HealthIndicatorsConfiguration.class }) public class HealthIndicatorAutoConfiguration { private final HealthIndicatorProperties properties; @@ -131,307 +88,4 @@ public class HealthIndicatorAutoConfiguration { return healthAggregator; } - @Bean - @ConditionalOnMissingBean(HealthIndicator.class) - public ApplicationHealthIndicator applicationHealthIndicator() { - return new ApplicationHealthIndicator(); - } - - @Configuration - @ConditionalOnClass({ CassandraOperations.class, Cluster.class }) - @ConditionalOnBean(CassandraOperations.class) - @ConditionalOnEnabledHealthIndicator("cassandra") - public static class CassandraHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map cassandraOperations; - - public CassandraHealthIndicatorConfiguration( - Map cassandraOperations) { - this.cassandraOperations = cassandraOperations; - } - - @Bean - @ConditionalOnMissingBean(name = "cassandraHealthIndicator") - public HealthIndicator cassandraHealthIndicator() { - return createHealthIndicator(this.cassandraOperations); - } - - } - - @Configuration - @ConditionalOnClass({ CouchbaseOperations.class, Bucket.class }) - @ConditionalOnBean(CouchbaseOperations.class) - @ConditionalOnEnabledHealthIndicator("couchbase") - public static class CouchbaseHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map couchbaseOperations; - - public CouchbaseHealthIndicatorConfiguration( - Map couchbaseOperations) { - this.couchbaseOperations = couchbaseOperations; - } - - @Bean - @ConditionalOnMissingBean(name = "couchbaseHealthIndicator") - public HealthIndicator couchbaseHealthIndicator() { - return createHealthIndicator(this.couchbaseOperations); - } - - } - - @Configuration - @ConditionalOnClass({ JdbcTemplate.class, AbstractRoutingDataSource.class }) - @ConditionalOnBean(DataSource.class) - @ConditionalOnEnabledHealthIndicator("db") - public static class DataSourcesHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration - implements InitializingBean { - - private final Map dataSources; - - private final Collection metadataProviders; - - private DataSourcePoolMetadataProvider poolMetadataProvider; - - public DataSourcesHealthIndicatorConfiguration( - ObjectProvider> dataSources, - ObjectProvider> metadataProviders) { - this.dataSources = filterDataSources(dataSources.getIfAvailable()); - this.metadataProviders = metadataProviders.getIfAvailable(); - } - - private Map filterDataSources( - Map candidates) { - if (candidates == null) { - return null; - } - Map dataSources = new LinkedHashMap<>(); - for (Map.Entry entry : candidates.entrySet()) { - if (!(entry.getValue() instanceof AbstractRoutingDataSource)) { - dataSources.put(entry.getKey(), entry.getValue()); - } - } - return dataSources; - } - - @Override - public void afterPropertiesSet() throws Exception { - this.poolMetadataProvider = new DataSourcePoolMetadataProviders( - this.metadataProviders); - } - - @Bean - @ConditionalOnMissingBean(name = "dbHealthIndicator") - public HealthIndicator dbHealthIndicator() { - return createHealthIndicator(this.dataSources); - } - - @Override - protected DataSourceHealthIndicator createHealthIndicator(DataSource source) { - return new DataSourceHealthIndicator(source, getValidationQuery(source)); - } - - private String getValidationQuery(DataSource source) { - DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider - .getDataSourcePoolMetadata(source); - return (poolMetadata == null ? null : poolMetadata.getValidationQuery()); - } - - } - - @Configuration - @ConditionalOnClass(LdapOperations.class) - @ConditionalOnBean(LdapOperations.class) - @ConditionalOnEnabledHealthIndicator("ldap") - public static class LdapHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map ldapOperations; - - public LdapHealthIndicatorConfiguration( - Map ldapOperations) { - this.ldapOperations = ldapOperations; - } - - @Bean - @ConditionalOnMissingBean(name = "ldapHealthIndicator") - public HealthIndicator ldapHealthIndicator() { - return createHealthIndicator(this.ldapOperations); - } - - } - - @Configuration - @ConditionalOnClass(MongoTemplate.class) - @ConditionalOnBean(MongoTemplate.class) - @ConditionalOnEnabledHealthIndicator("mongo") - public static class MongoHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map mongoTemplates; - - public MongoHealthIndicatorConfiguration( - Map mongoTemplates) { - this.mongoTemplates = mongoTemplates; - } - - @Bean - @ConditionalOnMissingBean(name = "mongoHealthIndicator") - public HealthIndicator mongoHealthIndicator() { - return createHealthIndicator(this.mongoTemplates); - } - - } - - @Configuration - @ConditionalOnClass(SessionFactory.class) - @ConditionalOnBean(SessionFactory.class) - @ConditionalOnEnabledHealthIndicator("neo4j") - public static class Neo4jHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map sessionFactories; - - public Neo4jHealthIndicatorConfiguration( - Map sessionFactories) { - this.sessionFactories = sessionFactories; - } - - @Bean - @ConditionalOnMissingBean(name = "neo4jHealthIndicator") - public HealthIndicator neo4jHealthIndicator() { - return createHealthIndicator(this.sessionFactories); - } - - } - - @Configuration - @ConditionalOnClass(RedisConnectionFactory.class) - @ConditionalOnBean(RedisConnectionFactory.class) - @ConditionalOnEnabledHealthIndicator("redis") - public static class RedisHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map redisConnectionFactories; - - public RedisHealthIndicatorConfiguration( - Map redisConnectionFactories) { - this.redisConnectionFactories = redisConnectionFactories; - } - - @Bean - @ConditionalOnMissingBean(name = "redisHealthIndicator") - public HealthIndicator redisHealthIndicator() { - return createHealthIndicator(this.redisConnectionFactories); - } - - } - - @Configuration - @ConditionalOnClass(RabbitTemplate.class) - @ConditionalOnBean(RabbitTemplate.class) - @ConditionalOnEnabledHealthIndicator("rabbit") - public static class RabbitHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map rabbitTemplates; - - public RabbitHealthIndicatorConfiguration( - Map rabbitTemplates) { - this.rabbitTemplates = rabbitTemplates; - } - - @Bean - @ConditionalOnMissingBean(name = "rabbitHealthIndicator") - public HealthIndicator rabbitHealthIndicator() { - return createHealthIndicator(this.rabbitTemplates); - } - - } - - @Configuration - @ConditionalOnClass(SolrClient.class) - @ConditionalOnBean(SolrClient.class) - @ConditionalOnEnabledHealthIndicator("solr") - public static class SolrHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map solrClients; - - public SolrHealthIndicatorConfiguration(Map solrClients) { - this.solrClients = solrClients; - } - - @Bean - @ConditionalOnMissingBean(name = "solrHealthIndicator") - public HealthIndicator solrHealthIndicator() { - return createHealthIndicator(this.solrClients); - } - - } - - @Configuration - @ConditionalOnEnabledHealthIndicator("diskspace") - public static class DiskSpaceHealthIndicatorConfiguration { - - @Bean - @ConditionalOnMissingBean(name = "diskSpaceHealthIndicator") - public DiskSpaceHealthIndicator diskSpaceHealthIndicator( - DiskSpaceHealthIndicatorProperties properties) { - return new DiskSpaceHealthIndicator(properties); - } - - @Bean - public DiskSpaceHealthIndicatorProperties diskSpaceHealthIndicatorProperties() { - return new DiskSpaceHealthIndicatorProperties(); - } - - } - - @Configuration - @ConditionalOnClass(JavaMailSenderImpl.class) - @ConditionalOnBean(JavaMailSenderImpl.class) - @ConditionalOnEnabledHealthIndicator("mail") - public static class MailHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map mailSenders; - - public MailHealthIndicatorConfiguration( - ObjectProvider> mailSenders) { - this.mailSenders = mailSenders.getIfAvailable(); - } - - @Bean - @ConditionalOnMissingBean(name = "mailHealthIndicator") - public HealthIndicator mailHealthIndicator() { - return createHealthIndicator(this.mailSenders); - } - - } - - @Configuration - @ConditionalOnClass(ConnectionFactory.class) - @ConditionalOnBean(ConnectionFactory.class) - @ConditionalOnEnabledHealthIndicator("jms") - public static class JmsHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map connectionFactories; - - public JmsHealthIndicatorConfiguration( - ObjectProvider> connectionFactories) { - this.connectionFactories = connectionFactories.getIfAvailable(); - } - - @Bean - @ConditionalOnMissingBean(name = "jmsHealthIndicator") - public HealthIndicator jmsHealthIndicator() { - return createHealthIndicator(this.connectionFactories); - } - - } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorsConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorsConfiguration.java new file mode 100644 index 0000000000..63fbd51c11 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorsConfiguration.java @@ -0,0 +1,381 @@ +/* + * Copyright 2012-2017 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.autoconfigure.health; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.jms.ConnectionFactory; +import javax.sql.DataSource; + +import com.couchbase.client.java.Bucket; +import com.datastax.driver.core.Cluster; +import org.apache.solr.client.solrj.SolrClient; +import org.neo4j.ogm.session.SessionFactory; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.health.ApplicationHealthIndicator; +import org.springframework.boot.actuate.health.CassandraHealthIndicator; +import org.springframework.boot.actuate.health.CouchbaseHealthIndicator; +import org.springframework.boot.actuate.health.DataSourceHealthIndicator; +import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; +import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.JmsHealthIndicator; +import org.springframework.boot.actuate.health.LdapHealthIndicator; +import org.springframework.boot.actuate.health.MailHealthIndicator; +import org.springframework.boot.actuate.health.MongoHealthIndicator; +import org.springframework.boot.actuate.health.Neo4jHealthIndicator; +import org.springframework.boot.actuate.health.RabbitHealthIndicator; +import org.springframework.boot.actuate.health.RedisHealthIndicator; +import org.springframework.boot.actuate.health.SolrHealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadata; +import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvider; +import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProviders; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +/** + * Configuration for available {@link HealthIndicator health indicators}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@Configuration +@Import({ + ElasticsearchHealthIndicatorConfiguration.ElasticsearchClientHealthIndicatorConfiguration.class, + ElasticsearchHealthIndicatorConfiguration.ElasticsearchJestHealthIndicatorConfiguration.class }) +public class HealthIndicatorsConfiguration { + + @Bean + @ConditionalOnMissingBean(HealthIndicator.class) + public ApplicationHealthIndicator applicationHealthIndicator() { + return new ApplicationHealthIndicator(); + } + + @Configuration + @ConditionalOnClass({ CassandraOperations.class, Cluster.class }) + @ConditionalOnBean(CassandraOperations.class) + @ConditionalOnEnabledHealthIndicator("cassandra") + public static class CassandraHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map cassandraOperations; + + public CassandraHealthIndicatorConfiguration( + Map cassandraOperations) { + this.cassandraOperations = cassandraOperations; + } + + @Bean + @ConditionalOnMissingBean(name = "cassandraHealthIndicator") + public HealthIndicator cassandraHealthIndicator() { + return createHealthIndicator(this.cassandraOperations); + } + + } + + @Configuration + @ConditionalOnClass({ CouchbaseOperations.class, Bucket.class }) + @ConditionalOnBean(CouchbaseOperations.class) + @ConditionalOnEnabledHealthIndicator("couchbase") + public static class CouchbaseHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map couchbaseOperations; + + public CouchbaseHealthIndicatorConfiguration( + Map couchbaseOperations) { + this.couchbaseOperations = couchbaseOperations; + } + + @Bean + @ConditionalOnMissingBean(name = "couchbaseHealthIndicator") + public HealthIndicator couchbaseHealthIndicator() { + return createHealthIndicator(this.couchbaseOperations); + } + + } + + @Configuration + @ConditionalOnClass({ JdbcTemplate.class, AbstractRoutingDataSource.class }) + @ConditionalOnBean(DataSource.class) + @ConditionalOnEnabledHealthIndicator("db") + public static class DataSourcesHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration + implements InitializingBean { + + private final Map dataSources; + + private final Collection metadataProviders; + + private DataSourcePoolMetadataProvider poolMetadataProvider; + + public DataSourcesHealthIndicatorConfiguration( + ObjectProvider> dataSources, + ObjectProvider> metadataProviders) { + this.dataSources = filterDataSources(dataSources.getIfAvailable()); + this.metadataProviders = metadataProviders.getIfAvailable(); + } + + private Map filterDataSources( + Map candidates) { + if (candidates == null) { + return null; + } + Map dataSources = new LinkedHashMap<>(); + for (Map.Entry entry : candidates.entrySet()) { + if (!(entry.getValue() instanceof AbstractRoutingDataSource)) { + dataSources.put(entry.getKey(), entry.getValue()); + } + } + return dataSources; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.poolMetadataProvider = new DataSourcePoolMetadataProviders( + this.metadataProviders); + } + + @Bean + @ConditionalOnMissingBean(name = "dbHealthIndicator") + public HealthIndicator dbHealthIndicator() { + return createHealthIndicator(this.dataSources); + } + + @Override + protected DataSourceHealthIndicator createHealthIndicator(DataSource source) { + return new DataSourceHealthIndicator(source, getValidationQuery(source)); + } + + private String getValidationQuery(DataSource source) { + DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider + .getDataSourcePoolMetadata(source); + return (poolMetadata == null ? null : poolMetadata.getValidationQuery()); + } + + } + + @Configuration + @ConditionalOnClass(LdapOperations.class) + @ConditionalOnBean(LdapOperations.class) + @ConditionalOnEnabledHealthIndicator("ldap") + public static class LdapHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map ldapOperations; + + public LdapHealthIndicatorConfiguration( + Map ldapOperations) { + this.ldapOperations = ldapOperations; + } + + @Bean + @ConditionalOnMissingBean(name = "ldapHealthIndicator") + public HealthIndicator ldapHealthIndicator() { + return createHealthIndicator(this.ldapOperations); + } + + } + + @Configuration + @ConditionalOnClass(MongoTemplate.class) + @ConditionalOnBean(MongoTemplate.class) + @ConditionalOnEnabledHealthIndicator("mongo") + public static class MongoHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map mongoTemplates; + + public MongoHealthIndicatorConfiguration( + Map mongoTemplates) { + this.mongoTemplates = mongoTemplates; + } + + @Bean + @ConditionalOnMissingBean(name = "mongoHealthIndicator") + public HealthIndicator mongoHealthIndicator() { + return createHealthIndicator(this.mongoTemplates); + } + + } + + @Configuration + @ConditionalOnClass(SessionFactory.class) + @ConditionalOnBean(SessionFactory.class) + @ConditionalOnEnabledHealthIndicator("neo4j") + public static class Neo4jHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map sessionFactories; + + public Neo4jHealthIndicatorConfiguration( + Map sessionFactories) { + this.sessionFactories = sessionFactories; + } + + @Bean + @ConditionalOnMissingBean(name = "neo4jHealthIndicator") + public HealthIndicator neo4jHealthIndicator() { + return createHealthIndicator(this.sessionFactories); + } + + } + + @Configuration + @ConditionalOnClass(RedisConnectionFactory.class) + @ConditionalOnBean(RedisConnectionFactory.class) + @ConditionalOnEnabledHealthIndicator("redis") + public static class RedisHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map redisConnectionFactories; + + public RedisHealthIndicatorConfiguration( + Map redisConnectionFactories) { + this.redisConnectionFactories = redisConnectionFactories; + } + + @Bean + @ConditionalOnMissingBean(name = "redisHealthIndicator") + public HealthIndicator redisHealthIndicator() { + return createHealthIndicator(this.redisConnectionFactories); + } + + } + + @Configuration + @ConditionalOnClass(RabbitTemplate.class) + @ConditionalOnBean(RabbitTemplate.class) + @ConditionalOnEnabledHealthIndicator("rabbit") + public static class RabbitHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map rabbitTemplates; + + public RabbitHealthIndicatorConfiguration( + Map rabbitTemplates) { + this.rabbitTemplates = rabbitTemplates; + } + + @Bean + @ConditionalOnMissingBean(name = "rabbitHealthIndicator") + public HealthIndicator rabbitHealthIndicator() { + return createHealthIndicator(this.rabbitTemplates); + } + + } + + @Configuration + @ConditionalOnClass(SolrClient.class) + @ConditionalOnBean(SolrClient.class) + @ConditionalOnEnabledHealthIndicator("solr") + public static class SolrHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map solrClients; + + public SolrHealthIndicatorConfiguration(Map solrClients) { + this.solrClients = solrClients; + } + + @Bean + @ConditionalOnMissingBean(name = "solrHealthIndicator") + public HealthIndicator solrHealthIndicator() { + return createHealthIndicator(this.solrClients); + } + + } + + @Configuration + @ConditionalOnEnabledHealthIndicator("diskspace") + public static class DiskSpaceHealthIndicatorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "diskSpaceHealthIndicator") + public DiskSpaceHealthIndicator diskSpaceHealthIndicator( + DiskSpaceHealthIndicatorProperties properties) { + return new DiskSpaceHealthIndicator(properties); + } + + @Bean + public DiskSpaceHealthIndicatorProperties diskSpaceHealthIndicatorProperties() { + return new DiskSpaceHealthIndicatorProperties(); + } + + } + + @Configuration + @ConditionalOnClass(JavaMailSenderImpl.class) + @ConditionalOnBean(JavaMailSenderImpl.class) + @ConditionalOnEnabledHealthIndicator("mail") + public static class MailHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map mailSenders; + + public MailHealthIndicatorConfiguration( + ObjectProvider> mailSenders) { + this.mailSenders = mailSenders.getIfAvailable(); + } + + @Bean + @ConditionalOnMissingBean(name = "mailHealthIndicator") + public HealthIndicator mailHealthIndicator() { + return createHealthIndicator(this.mailSenders); + } + + } + + @Configuration + @ConditionalOnClass(ConnectionFactory.class) + @ConditionalOnBean(ConnectionFactory.class) + @ConditionalOnEnabledHealthIndicator("jms") + public static class JmsHealthIndicatorConfiguration extends + CompositeHealthIndicatorConfiguration { + + private final Map connectionFactories; + + public JmsHealthIndicatorConfiguration( + ObjectProvider> connectionFactories) { + this.connectionFactories = connectionFactories.getIfAvailable(); + } + + @Bean + @ConditionalOnMissingBean(name = "jmsHealthIndicator") + public HealthIndicator jmsHealthIndicator() { + return createHealthIndicator(this.connectionFactories); + } + + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthIndicatorsConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthIndicatorsConfiguration.java new file mode 100644 index 0000000000..f9482bbccc --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthIndicatorsConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2017 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.autoconfigure.health; + +import java.util.Map; + +import reactor.core.publisher.Flux; + +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.RedisReactiveHealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; + +/** + * Configuration for available {@link ReactiveHealthIndicator reactive health indicators}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@Configuration +@ConditionalOnClass(Flux.class) +public class ReactiveHealthIndicatorsConfiguration { + + @Configuration + @ConditionalOnBean(ReactiveRedisConnectionFactory.class) + @ConditionalOnEnabledHealthIndicator("redis") + static class RedisHealthIndicatorConfiguration extends + CompositeReactiveHealthIndicatorConfiguration { + + private final Map redisConnectionFactories; + + RedisHealthIndicatorConfiguration( + Map redisConnectionFactories) { + this.redisConnectionFactories = redisConnectionFactories; + } + + @Bean + @ConditionalOnMissingBean(name = "redisHealthIndicator") + public ReactiveHealthIndicator redisHealthIndicator() { + return createHealthIndicator(this.redisConnectionFactories); + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthReactiveWebEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthReactiveWebEndpointExtension.java new file mode 100644 index 0000000000..9a7c56b354 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/HealthReactiveWebEndpointExtension.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2017 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.endpoint.web; + +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.web.WebEndpointExtension; +import org.springframework.boot.endpoint.web.WebEndpointResponse; + +/** + * Reactive {@link WebEndpointExtension} for the {@link HealthEndpoint}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@WebEndpointExtension(endpoint = HealthEndpoint.class) +public class HealthReactiveWebEndpointExtension { + + private final ReactiveHealthIndicator delegate; + + private final HealthStatusHttpMapper statusHttpMapper; + + public HealthReactiveWebEndpointExtension(ReactiveHealthIndicator delegate, + HealthStatusHttpMapper statusHttpMapper) { + this.delegate = delegate; + this.statusHttpMapper = statusHttpMapper; + } + + @ReadOperation + public Mono> health() { + return this.delegate.health().map(health -> { + Integer status = this.statusHttpMapper.mapStatus(health.getStatus()); + return new WebEndpointResponse<>(health, status); + }); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/StatusReactiveWebEndpointExtension.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/StatusReactiveWebEndpointExtension.java new file mode 100644 index 0000000000..96861d273f --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/StatusReactiveWebEndpointExtension.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2017 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.endpoint.web; + +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.endpoint.StatusEndpoint; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.endpoint.ReadOperation; +import org.springframework.boot.endpoint.web.WebEndpointExtension; +import org.springframework.boot.endpoint.web.WebEndpointResponse; + +/** + * Reactive {@link WebEndpointExtension} for the {@link StatusEndpoint}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@WebEndpointExtension(endpoint = StatusEndpoint.class) +public class StatusReactiveWebEndpointExtension { + + private final ReactiveHealthIndicator delegate; + + private final HealthStatusHttpMapper statusHttpMapper; + + public StatusReactiveWebEndpointExtension(ReactiveHealthIndicator delegate, + HealthStatusHttpMapper statusHttpMapper) { + this.delegate = delegate; + this.statusHttpMapper = statusHttpMapper; + } + + @ReadOperation + public Mono> health() { + return this.delegate.health().map(health -> { + Integer status = this.statusHttpMapper.mapStatus(health.getStatus()); + return new WebEndpointResponse<>( + Health.status(health.getStatus()).build(), status); + }); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractReactiveHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractReactiveHealthIndicator.java new file mode 100644 index 0000000000..dc42652998 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractReactiveHealthIndicator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2017 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 reactor.core.publisher.Mono; + +/** + * Base {@link ReactiveHealthIndicator} implementations that encapsulates creation of + * {@link Health} instance and error handling. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public abstract class AbstractReactiveHealthIndicator + implements ReactiveHealthIndicator { + + @Override + public final Mono health() { + return doHealthCheck(new Health.Builder()) + .onErrorResume(ex -> Mono.just(new Health.Builder().down(ex).build())); + } + + /** + * Actual health check logic. If an error occurs in the pipeline it will be + * handled automatically. + * @param builder the {@link Health.Builder} to report health status and details + * @return a {@link Mono} that provides the {@link Health} + */ + protected abstract Mono doHealthCheck(Health.Builder builder); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactory.java similarity index 69% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorFactory.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactory.java index 45b42068df..d6d4dc0717 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorFactory.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactory.java @@ -17,16 +17,28 @@ package org.springframework.boot.actuate.health; import java.util.Map; +import java.util.function.Function; import org.springframework.util.Assert; /** - * Factory to create a {@link HealthIndicator}. + * Factory to create a {@link CompositeHealthIndicator}. * * @author Stephane Nicoll * @since 2.0.0 */ -public class HealthIndicatorFactory { +public class CompositeHealthIndicatorFactory { + + private final Function healthIndicatorNameFactory; + + public CompositeHealthIndicatorFactory( + Function healthIndicatorNameFactory) { + this.healthIndicatorNameFactory = healthIndicatorNameFactory; + } + + public CompositeHealthIndicatorFactory() { + this(new HealthIndicatorNameFactory()); + } /** * Create a {@link CompositeHealthIndicator} based on the specified health indicators. @@ -35,30 +47,17 @@ public class HealthIndicatorFactory { * @return a {@link HealthIndicator} that delegates to the specified * {@code healthIndicators}. */ - public HealthIndicator createHealthIndicator(HealthAggregator healthAggregator, + public CompositeHealthIndicator createHealthIndicator(HealthAggregator healthAggregator, Map healthIndicators) { 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()); + String name = this.healthIndicatorNameFactory.apply(entry.getKey()); + healthIndicator.addHealthIndicator(name, entry.getValue()); } return healthIndicator; } - /** - * Turns the health indicator name into a key that can be used in the map of health - * information. - * @param name the health indicator 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/CompositeReactiveHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java new file mode 100644 index 0000000000..68040b2f18 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2017 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.time.Duration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; + +import org.springframework.util.Assert; + +/** + * {@link ReactiveHealthIndicator} that returns health indications from all registered + * delegates. Provides an alternative {@link Health} for a delegate that reaches a + * configurable timeout. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class CompositeReactiveHealthIndicator implements ReactiveHealthIndicator { + + private final Map indicators; + + private final HealthAggregator healthAggregator; + + private Long timeout; + + private Health timeoutHealth; + + private final Function, Mono> timeoutCompose; + + public CompositeReactiveHealthIndicator(HealthAggregator healthAggregator) { + this(healthAggregator, new LinkedHashMap<>()); + } + + public CompositeReactiveHealthIndicator(HealthAggregator healthAggregator, + Map indicators) { + Assert.notNull(healthAggregator, "HealthAggregator must not be null"); + Assert.notNull(indicators, "Indicators must not be null"); + this.indicators = new LinkedHashMap<>(indicators); + this.healthAggregator = healthAggregator; + this.timeoutCompose = mono -> this.timeout != null ? + mono.timeout(Duration.ofMillis(this.timeout), Mono.just(this.timeoutHealth)) : + mono; + } + + /** + * Add a {@link ReactiveHealthIndicator} with the specified name. + * @param name the name of the health indicator + * @param indicator the health indicator to add + * @return this instance + */ + public CompositeReactiveHealthIndicator addHealthIndicator(String name, + ReactiveHealthIndicator indicator) { + this.indicators.put(name, indicator); + return this; + } + + /** + * Specify an alternative timeout {@link Health} if an {@link HealthIndicator} failed + * to reply after specified {@code timeout}. + * @param timeout number of milliseconds to wait before using the {@code timeoutHealth} + * @param timeoutHealth the {@link Health} to use if an health indicator reached the + * {@code} timeout + * @return this instance + */ + public CompositeReactiveHealthIndicator timeoutStrategy(long timeout, + Health timeoutHealth) { + this.timeout = timeout; + this.timeoutHealth = (timeoutHealth != null ? timeoutHealth + : Health.unknown().build()); + return this; + } + + @Override + public Mono health() { + return Flux.fromIterable(this.indicators.entrySet()) + .flatMap(entry -> Mono.just(entry.getKey()) + .and(entry.getValue().health().compose(this.timeoutCompose))) + .collectMap(Tuple2::getT1, Tuple2::getT2) + .map(this.healthAggregator::aggregate); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorFactory.java new file mode 100644 index 0000000000..f32f5a5063 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2017 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.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Factory to create a {@link CompositeReactiveHealthIndicator}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class CompositeReactiveHealthIndicatorFactory { + + private final Function healthIndicatorNameFactory; + + public CompositeReactiveHealthIndicatorFactory( + Function healthIndicatorNameFactory) { + this.healthIndicatorNameFactory = healthIndicatorNameFactory; + } + + public CompositeReactiveHealthIndicatorFactory() { + this(new HealthIndicatorNameFactory()); + } + + /** + * Create a {@link CompositeReactiveHealthIndicator} based on the specified health + * indicators. Each {@link HealthIndicator} are wrapped to a + * {@link HealthIndicatorReactiveAdapter}. If two instances share the same name, the + * reactive variant takes precedence. + * @param healthAggregator the {@link HealthAggregator} + * @param reactiveHealthIndicators the {@link ReactiveHealthIndicator} instances + * mapped by name + * @param healthIndicators the {@link HealthIndicator} instances mapped by name if + * any. + * @return a {@link ReactiveHealthIndicator} that delegates to the specified + * {@code reactiveHealthIndicators}. + */ + public CompositeReactiveHealthIndicator createReactiveHealthIndicator( + HealthAggregator healthAggregator, + Map reactiveHealthIndicators, + Map healthIndicators) { + Assert.notNull(healthAggregator, "HealthAggregator must not be null"); + Assert.notNull(reactiveHealthIndicators, "ReactiveHealthIndicators must not be null"); + CompositeReactiveHealthIndicator healthIndicator = new CompositeReactiveHealthIndicator( + healthAggregator); + merge(reactiveHealthIndicators, healthIndicators).forEach((beanName, indicator) -> { + String name = this.healthIndicatorNameFactory.apply(beanName); + healthIndicator.addHealthIndicator(name, indicator); + }); + return healthIndicator; + } + + private Map merge( + Map reactiveHealthIndicators, + Map healthIndicators) { + if (ObjectUtils.isEmpty(healthIndicators)) { + return reactiveHealthIndicators; + } + Map allIndicators = new LinkedHashMap<>( + reactiveHealthIndicators); + healthIndicators.forEach((beanName, indicator) -> { + String name = this.healthIndicatorNameFactory.apply(beanName); + allIndicators.computeIfAbsent(name, n -> + new HealthIndicatorReactiveAdapter(indicator)); + }); + return allIndicators; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java index ab7c9cc5fe..771e6a8805 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java @@ -213,7 +213,7 @@ public final class Health { * @param ex the exception * @return this {@link Builder} instance */ - public Builder withException(Exception ex) { + public Builder withException(Throwable ex) { Assert.notNull(ex, "Exception must not be null"); return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage()); } @@ -248,11 +248,11 @@ public final class Health { } /** - * Set status to {@link Status#DOWN} and add details for given {@link Exception}. + * Set status to {@link Status#DOWN} and add details for given {@link Throwable}. * @param ex the exception * @return this {@link Builder} instance */ - public Builder down(Exception ex) { + public Builder down(Throwable ex) { return down().withException(ex); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorNameFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorNameFactory.java new file mode 100644 index 0000000000..5c31b5a9b4 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorNameFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2017 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.function.Function; + +/** + * Generate a sensible health indicator name based on its bean name. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class HealthIndicatorNameFactory implements Function { + + @Override + public String apply(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/HealthIndicatorReactiveAdapter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java new file mode 100644 index 0000000000..e3d3adf525 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2017 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 reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import reactor.core.scheduler.Schedulers; + +import org.springframework.util.Assert; + +/** + * Adapts a {@link HealthIndicator} to a {@link ReactiveHealthIndicator} so that it can + * be safely invoked in a reactive environment. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class HealthIndicatorReactiveAdapter implements ReactiveHealthIndicator { + + private final HealthIndicator delegate; + + public HealthIndicatorReactiveAdapter(HealthIndicator delegate) { + Assert.notNull(delegate, "Delegate must not be null"); + this.delegate = delegate; + } + + @Override + public Mono health() { + return Mono.create((sink) -> + Schedulers.elastic().schedule(() -> invoke(sink))); + } + + private void invoke(MonoSink sink) { + try { + Health health = this.delegate.health(); + sink.success(health); + } + catch (Exception ex) { + sink.error(ex); + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java new file mode 100644 index 0000000000..455f2bfd41 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2017 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 reactor.core.publisher.Mono; + +/** + * Defines the {@link Health} of an arbitrary system or component. + *

+ * This is non blocking contract that is meant to be used in a reactive application. See + * {@link HealthIndicator} for the traditional contract. + * + * @author Stephane Nicoll + * @since 2.0.0 + * @see HealthIndicator + */ +@FunctionalInterface +public interface ReactiveHealthIndicator { + + /** + * Provide the indicator of health. + * @return a {@link Mono} that provides the {@link Health} + */ + Mono health(); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java index 96e4a6838c..85f5c0c700 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java @@ -35,9 +35,9 @@ import org.springframework.util.Assert; */ public class RedisHealthIndicator extends AbstractHealthIndicator { - private static final String VERSION = "version"; + static final String VERSION = "version"; - private static final String REDIS_VERSION = "redis_version"; + static final String REDIS_VERSION = "redis_version"; private final RedisConnectionFactory redisConnectionFactory; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisReactiveHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisReactiveHealthIndicator.java new file mode 100644 index 0000000000..6b44f96ec7 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisReactiveHealthIndicator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2017 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 reactor.core.publisher.Mono; + +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; + +/** + * A {@link ReactiveHealthIndicator} for Redis. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class RedisReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + + private final ReactiveRedisConnectionFactory connectionFactory; + + public RedisReactiveHealthIndicator( + ReactiveRedisConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + @Override + protected Mono doHealthCheck(Health.Builder builder) { + return this.connectionFactory.getReactiveConnection().serverCommands().info() + .map(info -> builder.up().withDetail( + RedisHealthIndicator.VERSION, info.getProperty( + RedisHealthIndicator.REDIS_VERSION)).build()); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java new file mode 100644 index 0000000000..357bc11367 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfigurationTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2017 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.autoconfigure.endpoint; + +import org.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link EndpointAutoConfiguration}. + * + * @author Stephane Nicoll + */ +public class EndpointAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class)); + + @Test + public void healthEndpointAdaptReactiveHealthIndicator() { + this.contextRunner.withUserConfiguration( + ReactiveHealthIndicatorConfiguration.class).run((context) -> { + ReactiveHealthIndicator reactiveHealthIndicator = context.getBean( + "reactiveHealthIndicator", ReactiveHealthIndicator.class); + verify(reactiveHealthIndicator, times(0)).health(); + Health health = context.getBean(HealthEndpoint.class).health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsOnlyKeys("reactive"); + verify(reactiveHealthIndicator, times(1)).health(); + }); + } + + @Test + public void healthEndpointMergeRegularAndReactive() { + this.contextRunner.withUserConfiguration(HealthIndicatorConfiguration.class, + ReactiveHealthIndicatorConfiguration.class).run((context) -> { + HealthIndicator simpleHealthIndicator = context.getBean( + "simpleHealthIndicator", HealthIndicator.class); + ReactiveHealthIndicator reactiveHealthIndicator = context.getBean( + "reactiveHealthIndicator", ReactiveHealthIndicator.class); + verify(simpleHealthIndicator, times(0)).health(); + verify(reactiveHealthIndicator, times(0)).health(); + Health health = context.getBean(HealthEndpoint.class).health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsOnlyKeys("simple", "reactive"); + verify(simpleHealthIndicator, times(1)).health(); + verify(reactiveHealthIndicator, times(1)).health(); + }); + } + + + @Configuration + static class HealthIndicatorConfiguration { + + @Bean + public HealthIndicator simpleHealthIndicator() { + HealthIndicator mock = mock(HealthIndicator.class); + given(mock.health()).willReturn(Health.status(Status.UP).build()); + return mock; + } + + } + + @Configuration + static class ReactiveHealthIndicatorConfiguration { + + @Bean + public ReactiveHealthIndicator reactiveHealthIndicator() { + ReactiveHealthIndicator mock = mock(ReactiveHealthIndicator.class); + given(mock.health()).willReturn(Mono.just(Health.status(Status.UP).build())); + return mock; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java index 87c2360e84..33d02b82b3 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointManagementContextConfigurationTests.java @@ -25,14 +25,17 @@ import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.StatusEndpoint; import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension; +import org.springframework.boot.actuate.endpoint.web.HealthReactiveWebEndpointExtension; import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint; import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.StatusReactiveWebEndpointExtension; import org.springframework.boot.actuate.endpoint.web.StatusWebEndpointExtension; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.util.ReflectionTestUtils; @@ -67,7 +70,7 @@ public class WebEndpointManagementContextConfigurationTests { @Test public void healthStatusMappingCanBeCustomized() { - ApplicationContextRunner contextRunner = contextRunner() + WebApplicationContextRunner contextRunner = webContextRunner() .withPropertyValues("management.health.status.http-mapping.CUSTOM=500") .withUserConfiguration(HealthEndpointConfiguration.class); contextRunner.run((context) -> { @@ -94,7 +97,7 @@ public class WebEndpointManagementContextConfigurationTests { @Test public void statusMappingCanBeCustomized() { - ApplicationContextRunner contextRunner = contextRunner() + WebApplicationContextRunner contextRunner = webContextRunner() .withPropertyValues("management.health.status.http-mapping.CUSTOM=500") .withUserConfiguration(StatusEndpointConfiguration.class); contextRunner.run((context) -> { @@ -113,6 +116,70 @@ public class WebEndpointManagementContextConfigurationTests { "status", StatusEndpointConfiguration.class); } + @Test + public void reactiveHealthWebEndpointExtensionIsAutoConfigured() { + reactiveWebContextRunner(HealthEndpointConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(HealthReactiveWebEndpointExtension.class); + assertThat(context).doesNotHaveBean(HealthWebEndpointExtension.class); + }); + + } + + @Test + public void reactiveHealthStatusMappingCanBeCustomized() { + reactiveWebContextRunner(HealthEndpointConfiguration.class) + .withPropertyValues("management.health.status.http-mapping.CUSTOM=500") + .run((context) -> { + HealthReactiveWebEndpointExtension extension = context + .getBean(HealthReactiveWebEndpointExtension.class); + Map statusMappings = getStatusMapping(extension); + assertThat(statusMappings).containsEntry("DOWN", 503); + assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503); + assertThat(statusMappings).containsEntry("CUSTOM", 500); + }); + } + + @Test + public void reactiveHealthWebEndpointExtensionCanBeDisabled() { + reactiveWebContextRunner(HealthEndpointConfiguration.class) + .withPropertyValues("endpoints.health.enabled=false").run((context) -> { + assertThat(context).doesNotHaveBean(HealthReactiveWebEndpointExtension.class); + assertThat(context).doesNotHaveBean(HealthWebEndpointExtension.class); + }); + + } + + @Test + public void reactiveStatusWebEndpointExtensionIsAutoConfigured() { + reactiveWebContextRunner(StatusEndpointConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(StatusReactiveWebEndpointExtension.class); + assertThat(context).doesNotHaveBean(StatusWebEndpointExtension.class); + }); + } + + @Test + public void reactiveStatusMappingCanBeCustomized() { + reactiveWebContextRunner(StatusEndpointConfiguration.class) + .withPropertyValues("management.health.status.http-mapping.CUSTOM=500") + .run((context) -> { + StatusReactiveWebEndpointExtension extension = context + .getBean(StatusReactiveWebEndpointExtension.class); + Map statusMappings = getStatusMapping(extension); + assertThat(statusMappings).containsEntry("DOWN", 503); + assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503); + assertThat(statusMappings).containsEntry("CUSTOM", 500); + }); + } + + @Test + public void reactiveStatusWebEndpointExtensionCanBeDisabled() { + reactiveWebContextRunner(StatusEndpointConfiguration.class) + .withPropertyValues("endpoints.status.enabled=false").run((context) -> { + assertThat(context).doesNotHaveBean(StatusReactiveWebEndpointExtension.class); + assertThat(context).doesNotHaveBean(StatusWebEndpointExtension.class); + }); + } + @Test public void auditEventsWebEndpointExtensionIsAutoConfigured() { beanIsAutoConfigured(AuditEventsWebEndpointExtension.class, @@ -128,28 +195,28 @@ public class WebEndpointManagementContextConfigurationTests { @Test public void logFileWebEndpointIsAutoConfiguredWhenLoggingFileIsSet() { - contextRunner().withPropertyValues("logging.file:test.log").run( + webContextRunner().withPropertyValues("logging.file:test.log").run( (context) -> assertThat(context.getBeansOfType(LogFileWebEndpoint.class)) .hasSize(1)); } @Test public void logFileWebEndpointIsAutoConfiguredWhenLoggingPathIsSet() { - contextRunner().withPropertyValues("logging.path:test/logs").run( + webContextRunner().withPropertyValues("logging.path:test/logs").run( (context) -> assertThat(context.getBeansOfType(LogFileWebEndpoint.class)) .hasSize(1)); } @Test public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() { - contextRunner().withPropertyValues("endpoints.logfile.external-file:external.log") + webContextRunner().withPropertyValues("endpoints.logfile.external-file:external.log") .run((context) -> assertThat( context.getBeansOfType(LogFileWebEndpoint.class)).hasSize(1)); } @Test public void logFileWebEndpointCanBeDisabled() { - contextRunner() + webContextRunner() .withPropertyValues("logging.file:test.log", "endpoints.logfile.enabled:false") .run((context) -> assertThat(context) @@ -157,19 +224,31 @@ public class WebEndpointManagementContextConfigurationTests { } private void beanIsAutoConfigured(Class beanType, Class... config) { - contextRunner().withPropertyValues("endpoints.default.web.enabled:true") + webContextRunner().withPropertyValues("endpoints.default.web.enabled:true") .withUserConfiguration(config) .run((context) -> assertThat(context).hasSingleBean(beanType)); } + private ReactiveWebApplicationContextRunner reactiveWebContextRunner( + Class... config) { + return reactiveWebContextRunner() + .withPropertyValues("endpoints.default.web.enabled:true") + .withUserConfiguration(config); + } + private void beanIsNotAutoConfiguredWhenEndpointIsDisabled(Class webExtension, String id, Class... config) { - contextRunner().withPropertyValues("endpoints." + id + ".enabled=false") + webContextRunner().withPropertyValues("endpoints." + id + ".enabled=false") .run((context) -> assertThat(context).doesNotHaveBean(webExtension)); } - private ApplicationContextRunner contextRunner() { - return new ApplicationContextRunner().withConfiguration( + private WebApplicationContextRunner webContextRunner() { + return new WebApplicationContextRunner().withConfiguration( + AutoConfigurations.of(WebEndpointManagementContextConfiguration.class)); + } + + private ReactiveWebApplicationContextRunner reactiveWebContextRunner() { + return new ReactiveWebApplicationContextRunner().withConfiguration( AutoConfigurations.of(WebEndpointManagementContextConfiguration.class)); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfigurationTests.java index 29ed402976..05bef5714e 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfigurationTests.java @@ -16,501 +16,37 @@ package org.springframework.boot.actuate.autoconfigure.health; -import java.util.Map; - -import javax.sql.DataSource; - -import io.searchbox.client.JestClient; -import org.assertj.core.api.Condition; import org.junit.Test; -import org.neo4j.ogm.session.SessionFactory; -import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties; -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.CouchbaseHealthIndicator; -import org.springframework.boot.actuate.health.DataSourceHealthIndicator; -import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; -import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; -import org.springframework.boot.actuate.health.ElasticsearchJestHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.JmsHealthIndicator; -import org.springframework.boot.actuate.health.LdapHealthIndicator; -import org.springframework.boot.actuate.health.MailHealthIndicator; -import org.springframework.boot.actuate.health.MongoHealthIndicator; -import org.springframework.boot.actuate.health.Neo4jHealthIndicator; -import org.springframework.boot.actuate.health.RabbitHealthIndicator; -import org.springframework.boot.actuate.health.RedisHealthIndicator; -import org.springframework.boot.actuate.health.SolrHealthIndicator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.RedisReactiveHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; -import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration; -import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration; -import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; -import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; -import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; -import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.test.context.runner.ContextConsumer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.cassandra.core.CassandraOperations; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; -import org.springframework.ldap.core.LdapOperations; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; /** - * Tests for {@link HealthIndicatorAutoConfiguration}. + * Tests for {@link HealthIndicatorAutoConfiguration} that validates the outcome of + * combining reactive and non reactive health indicators. * - * @author Christian Dupuis * @author Stephane Nicoll - * @author Andy Wilkinson - * @author Eddú Meléndez - * @author Eric Spiegelberg */ public class HealthIndicatorAutoConfigurationTests { public final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration( - AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, - ManagementServerProperties.class)); + AutoConfigurations.of(HealthIndicatorAutoConfiguration.class)); @Test - public void defaultHealthIndicator() { - this.contextRunner.withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void defaultHealthIndicatorsDisabled() { - this.contextRunner.withPropertyValues("management.health.defaults.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void defaultHealthIndicatorsDisabledWithCustomOne() { - this.contextRunner.withUserConfiguration(CustomHealthIndicator.class) - .withPropertyValues("management.health.defaults.enabled:false") - .run((context) -> { - Map beans = context - .getBeansOfType(HealthIndicator.class); - assertThat(beans).hasSize(1); - assertThat(context.getBean("customHealthIndicator")) - .isSameAs(beans.values().iterator().next()); - }); - } - - @Test - public void defaultHealthIndicatorsDisabledButOne() { - this.contextRunner - .withPropertyValues("management.health.defaults.enabled:false", - "management.health.diskspace.enabled:true") - .run(hasSingleHealthIndicator(DiskSpaceHealthIndicator.class)); - } - - @Test - public void redisHealthIndicator() { + public void reactiveRedisTakePrecedence() { this.contextRunner .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(RedisHealthIndicator.class)); - } - - @Test - public void notRedisHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) - .withPropertyValues("management.health.redis.enabled:false", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void mongoHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, - MongoDataAutoConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(MongoHealthIndicator.class)); - } - - @Test - public void notMongoHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, - MongoDataAutoConfiguration.class)) - .withPropertyValues("management.health.mongo.enabled:false", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void combinedHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, - RedisAutoConfiguration.class, MongoDataAutoConfiguration.class, - SolrAutoConfiguration.class)) .run((context) -> { - Map beans = context - .getBeansOfType(HealthIndicator.class); - assertThat(beans).hasSize(4); + assertThat(context).hasSingleBean(ReactiveHealthIndicator.class); + assertThat(context).getBean("redisHealthIndicator") + .isInstanceOf(RedisReactiveHealthIndicator.class); }); } - @Test - public void dataSourceHealthIndicator() { - this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(DataSourceHealthIndicator.class)); - } - - @Test - public void dataSourceHealthIndicatorWithSeveralDataSources() { - this.contextRunner - .withUserConfiguration(EmbeddedDataSourceConfiguration.class, - DataSourceConfig.class) - .withPropertyValues("management.health.diskspace.enabled:false") - .run((context) -> { - Map beans = context - .getBeansOfType(HealthIndicator.class); - assertThat(beans).hasSize(1); - HealthIndicator bean = beans.values().iterator().next(); - assertThat(bean).isExactlyInstanceOf(CompositeHealthIndicator.class); - assertThat(bean.health().getDetails()).containsOnlyKeys("dataSource", - "testDataSource"); - }); - } - - @Test - public void dataSourceHealthIndicatorWithAbstractRoutingDataSource() { - this.contextRunner - .withUserConfiguration(EmbeddedDataSourceConfiguration.class, - RoutingDatasourceConfig.class) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(DataSourceHealthIndicator.class)); - } - - @Test - public void dataSourceHealthIndicatorWithCustomValidationQuery() { - this.contextRunner - .withUserConfiguration(DataSourceConfig.class, - DataSourcePoolMetadataProvidersConfiguration.class, - HealthIndicatorAutoConfiguration.class) - .withPropertyValues( - "spring.datasource.test.validation-query:SELECT from FOOBAR", - "management.health.diskspace.enabled:false") - .run((context) -> { - Map beans = context - .getBeansOfType(HealthIndicator.class); - assertThat(beans).hasSize(1); - HealthIndicator healthIndicator = beans.values().iterator().next(); - assertThat(healthIndicator.getClass()) - .isEqualTo(DataSourceHealthIndicator.class); - DataSourceHealthIndicator dataSourceHealthIndicator = (DataSourceHealthIndicator) healthIndicator; - assertThat(dataSourceHealthIndicator.getQuery()) - .isEqualTo("SELECT from FOOBAR"); - }); - } - - @Test - public void notDataSourceHealthIndicator() { - this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("management.health.db.enabled:false", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void rabbitHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(RabbitHealthIndicator.class)); - } - - @Test - public void notRabbitHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)) - .withPropertyValues("management.health.rabbit.enabled:false", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void solrHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(SolrAutoConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(SolrHealthIndicator.class)); - } - - @Test - public void notSolrHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(SolrAutoConfiguration.class)) - .withPropertyValues("management.health.solr.enabled:false", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void diskSpaceHealthIndicator() { - this.contextRunner.run(hasSingleHealthIndicator(DiskSpaceHealthIndicator.class)); - } - - @Test - public void mailHealthIndicator() { - this.contextRunner - .withConfiguration( - AutoConfigurations.of(MailSenderAutoConfiguration.class)) - .withPropertyValues("spring.mail.host:smtp.acme.org", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(MailHealthIndicator.class)); - } - - @Test - public void notMailHealthIndicator() { - this.contextRunner - .withConfiguration( - AutoConfigurations.of(MailSenderAutoConfiguration.class)) - .withPropertyValues("spring.mail.host:smtp.acme.org", - "management.health.mail.enabled:false", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void jmsHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(JmsHealthIndicator.class)); - } - - @Test - public void notJmsHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class)) - .withPropertyValues("management.health.jms.enabled:false", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void elasticsearchHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(JestClientConfiguration.class, - JestAutoConfiguration.class, - ElasticsearchAutoConfiguration.class)) - .withPropertyValues("spring.data.elasticsearch.cluster-nodes:localhost:0", - "management.health.diskspace.enabled:false") - .withSystemProperties("es.set.netty.runtime.available.processors=false") - .run(hasSingleHealthIndicator(ElasticsearchHealthIndicator.class)); - } - - @Test - public void elasticsearchJestHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(JestClientConfiguration.class, - JestAutoConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .withSystemProperties("es.set.netty.runtime.available.processors=false") - .run(hasSingleHealthIndicator(ElasticsearchJestHealthIndicator.class)); - } - - @Test - public void notElasticsearchHealthIndicator() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(JestClientConfiguration.class, - JestAutoConfiguration.class, - ElasticsearchAutoConfiguration.class)) - .withPropertyValues("management.health.elasticsearch.enabled:false", - "spring.data.elasticsearch.properties.path.home:target", - "management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void cassandraHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(CassandraHealthIndicator.class)); - } - - @Test - public void notCassandraHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false", - "management.health.cassandra.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void couchbaseHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(CouchbaseConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(CouchbaseHealthIndicator.class)); - } - - @Test - public void notCouchbaseHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(CouchbaseConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false", - "management.health.couchbase.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void ldapHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(LdapConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(LdapHealthIndicator.class)); - } - - @Test - public void notLdapHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(LdapConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false", - "management.health.ldap.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - @Test - public void neo4jHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(Neo4jConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false") - .run(hasSingleHealthIndicator(Neo4jHealthIndicator.class)); - } - - @Test - public void notNeo4jHealthIndicator() throws Exception { - this.contextRunner - .withConfiguration(AutoConfigurations.of(Neo4jConfiguration.class)) - .withPropertyValues("management.health.diskspace.enabled:false", - "management.health.neo4j.enabled:false") - .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); - } - - private ContextConsumer hasSingleHealthIndicator( - Class type) { - return (context) -> assertThat(context).getBeans(HealthIndicator.class).hasSize(1) - .hasValueSatisfying( - new Condition<>((indicator) -> indicator.getClass().equals(type), - "Wrong indicator type")); - } - - @Configuration - @EnableConfigurationProperties - protected static class DataSourceConfig { - - @Bean - @ConfigurationProperties(prefix = "spring.datasource.test") - public DataSource testDataSource() { - return DataSourceBuilder.create() - .type(org.apache.tomcat.jdbc.pool.DataSource.class) - .driverClassName("org.hsqldb.jdbc.JDBCDriver") - .url("jdbc:hsqldb:mem:test").username("sa").build(); - } - - } - - @Configuration - protected static class RoutingDatasourceConfig { - - @Bean - AbstractRoutingDataSource routingDataSource() { - return mock(AbstractRoutingDataSource.class); - } - - } - - @Configuration - protected static class CustomHealthIndicator { - - @Bean - public HealthIndicator customHealthIndicator() { - return () -> Health.down().build(); - } - - } - - @Configuration - @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) - protected static class CassandraConfiguration { - - @Bean - public CassandraOperations cassandraOperations() { - return mock(CassandraOperations.class); - } - - } - - @Configuration - @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) - protected static class CouchbaseConfiguration { - - @Bean - public CouchbaseOperations couchbaseOperations() { - return mock(CouchbaseOperations.class); - } - - } - - @Configuration - protected static class JestClientConfiguration { - - @Bean - public JestClient jestClient() { - return mock(JestClient.class); - } - - } - - @Configuration - @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) - protected static class LdapConfiguration { - - @Bean - public LdapOperations ldapOperations() { - return mock(LdapOperations.class); - } - - } - - @Configuration - @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) - protected static class Neo4jConfiguration { - - @Bean - public SessionFactory sessionFactory() { - return mock(SessionFactory.class); - } - - } - } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorsConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorsConfigurationTests.java new file mode 100644 index 0000000000..c5b396f02e --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorsConfigurationTests.java @@ -0,0 +1,519 @@ +/* + * Copyright 2012-2017 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.autoconfigure.health; + +import java.util.Map; + +import javax.sql.DataSource; + +import io.searchbox.client.JestClient; +import org.assertj.core.api.Condition; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.ogm.session.SessionFactory; + +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.CouchbaseHealthIndicator; +import org.springframework.boot.actuate.health.DataSourceHealthIndicator; +import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; +import org.springframework.boot.actuate.health.ElasticsearchJestHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.JmsHealthIndicator; +import org.springframework.boot.actuate.health.LdapHealthIndicator; +import org.springframework.boot.actuate.health.MailHealthIndicator; +import org.springframework.boot.actuate.health.MongoHealthIndicator; +import org.springframework.boot.actuate.health.Neo4jHealthIndicator; +import org.springframework.boot.actuate.health.RabbitHealthIndicator; +import org.springframework.boot.actuate.health.RedisHealthIndicator; +import org.springframework.boot.actuate.health.SolrHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; +import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions; +import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import org.springframework.ldap.core.LdapOperations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HealthIndicatorsConfiguration}. + * + * @author Christian Dupuis + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Eddú Meléndez + * @author Eric Spiegelberg + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathExclusions({ "reactor-core*.jar", "lettuce-core*.jar" }) +public class HealthIndicatorsConfigurationTests { + + public final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(HealthIndicatorAutoConfiguration.class)); + + @Test + public void defaultHealthIndicator() { + this.contextRunner.withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void defaultHealthIndicatorsDisabled() { + this.contextRunner.withPropertyValues("management.health.defaults.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void defaultHealthIndicatorsDisabledWithCustomOne() { + this.contextRunner.withUserConfiguration(CustomHealthIndicator.class) + .withPropertyValues("management.health.defaults.enabled:false") + .run((context) -> { + Map beans = context + .getBeansOfType(HealthIndicator.class); + assertThat(beans).hasSize(1); + assertThat(context.getBean("customHealthIndicator")) + .isSameAs(beans.values().iterator().next()); + }); + } + + @Test + public void defaultHealthIndicatorsDisabledButOne() { + this.contextRunner + .withPropertyValues("management.health.defaults.enabled:false", + "management.health.diskspace.enabled:true") + .run(hasSingleHealthIndicator(DiskSpaceHealthIndicator.class)); + } + + @Test + public void redisHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(RedisHealthIndicator.class)); + } + + @Test + public void notRedisHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withPropertyValues("management.health.redis.enabled:false", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void mongoHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(MongoHealthIndicator.class)); + } + + @Test + public void notMongoHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class)) + .withPropertyValues("management.health.mongo.enabled:false", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void combinedHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, + RedisAutoConfiguration.class, MongoDataAutoConfiguration.class, + SolrAutoConfiguration.class)) + .run((context) -> { + Map beans = context + .getBeansOfType(HealthIndicator.class); + assertThat(beans).hasSize(4); + }); + } + + @Test + public void dataSourceHealthIndicator() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(DataSourceAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(DataSourceHealthIndicator.class)); + } + + @Test + public void dataSourceHealthIndicatorWithSeveralDataSources() { + this.contextRunner + .withUserConfiguration(EmbeddedDataSourceConfiguration.class, + DataSourceConfig.class) + .withPropertyValues("management.health.diskspace.enabled:false") + .run((context) -> { + Map beans = context + .getBeansOfType(HealthIndicator.class); + assertThat(beans).hasSize(1); + HealthIndicator bean = beans.values().iterator().next(); + assertThat(bean).isExactlyInstanceOf(CompositeHealthIndicator.class); + assertThat(bean.health().getDetails()).containsOnlyKeys("dataSource", + "testDataSource"); + }); + } + + @Test + public void dataSourceHealthIndicatorWithAbstractRoutingDataSource() { + this.contextRunner + .withUserConfiguration(EmbeddedDataSourceConfiguration.class, + RoutingDatasourceConfig.class) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(DataSourceHealthIndicator.class)); + } + + @Test + public void dataSourceHealthIndicatorWithCustomValidationQuery() { + this.contextRunner + .withUserConfiguration(DataSourceConfig.class, + DataSourcePoolMetadataProvidersConfiguration.class, + HealthIndicatorAutoConfiguration.class) + .withPropertyValues( + "spring.datasource.test.validation-query:SELECT from FOOBAR", + "management.health.diskspace.enabled:false") + .run((context) -> { + Map beans = context + .getBeansOfType(HealthIndicator.class); + assertThat(beans).hasSize(1); + HealthIndicator healthIndicator = beans.values().iterator().next(); + assertThat(healthIndicator.getClass()) + .isEqualTo(DataSourceHealthIndicator.class); + DataSourceHealthIndicator dataSourceHealthIndicator = (DataSourceHealthIndicator) healthIndicator; + assertThat(dataSourceHealthIndicator.getQuery()) + .isEqualTo("SELECT from FOOBAR"); + }); + } + + @Test + public void notDataSourceHealthIndicator() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("management.health.db.enabled:false", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void rabbitHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(RabbitHealthIndicator.class)); + } + + @Test + public void notRabbitHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)) + .withPropertyValues("management.health.rabbit.enabled:false", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void solrHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(SolrAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(SolrHealthIndicator.class)); + } + + @Test + public void notSolrHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(SolrAutoConfiguration.class)) + .withPropertyValues("management.health.solr.enabled:false", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void diskSpaceHealthIndicator() { + this.contextRunner.run(hasSingleHealthIndicator(DiskSpaceHealthIndicator.class)); + } + + @Test + public void mailHealthIndicator() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(MailSenderAutoConfiguration.class)) + .withPropertyValues("spring.mail.host:smtp.acme.org", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(MailHealthIndicator.class)); + } + + @Test + public void notMailHealthIndicator() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(MailSenderAutoConfiguration.class)) + .withPropertyValues("spring.mail.host:smtp.acme.org", + "management.health.mail.enabled:false", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void jmsHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(JmsHealthIndicator.class)); + } + + @Test + public void notJmsHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class)) + .withPropertyValues("management.health.jms.enabled:false", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void elasticsearchHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(JestClientConfiguration.class, + JestAutoConfiguration.class, + ElasticsearchAutoConfiguration.class)) + .withPropertyValues("spring.data.elasticsearch.cluster-nodes:localhost:0", + "management.health.diskspace.enabled:false") + .withSystemProperties("es.set.netty.runtime.available.processors=false") + .run(hasSingleHealthIndicator(ElasticsearchHealthIndicator.class)); + } + + @Test + public void elasticsearchJestHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(JestClientConfiguration.class, + JestAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .withSystemProperties("es.set.netty.runtime.available.processors=false") + .run(hasSingleHealthIndicator(ElasticsearchJestHealthIndicator.class)); + } + + @Test + public void notElasticsearchHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(JestClientConfiguration.class, + JestAutoConfiguration.class, + ElasticsearchAutoConfiguration.class)) + .withPropertyValues("management.health.elasticsearch.enabled:false", + "spring.data.elasticsearch.properties.path.home:target", + "management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void cassandraHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(CassandraHealthIndicator.class)); + } + + @Test + public void notCassandraHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false", + "management.health.cassandra.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void couchbaseHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(CouchbaseConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(CouchbaseHealthIndicator.class)); + } + + @Test + public void notCouchbaseHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(CouchbaseConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false", + "management.health.couchbase.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void ldapHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(LdapConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(LdapHealthIndicator.class)); + } + + @Test + public void notLdapHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(LdapConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false", + "management.health.ldap.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + @Test + public void neo4jHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(Neo4jConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleHealthIndicator(Neo4jHealthIndicator.class)); + } + + @Test + public void notNeo4jHealthIndicator() throws Exception { + this.contextRunner + .withConfiguration(AutoConfigurations.of(Neo4jConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false", + "management.health.neo4j.enabled:false") + .run(hasSingleHealthIndicator(ApplicationHealthIndicator.class)); + } + + private ContextConsumer hasSingleHealthIndicator( + Class type) { + return (context) -> assertThat(context).getBeans(HealthIndicator.class).hasSize(1) + .hasValueSatisfying( + new Condition<>((indicator) -> indicator.getClass().equals(type), + "Wrong indicator type")); + } + + @Configuration + @EnableConfigurationProperties + protected static class DataSourceConfig { + + @Bean + @ConfigurationProperties(prefix = "spring.datasource.test") + public DataSource testDataSource() { + return DataSourceBuilder.create() + .type(org.apache.tomcat.jdbc.pool.DataSource.class) + .driverClassName("org.hsqldb.jdbc.JDBCDriver") + .url("jdbc:hsqldb:mem:test").username("sa").build(); + } + + } + + @Configuration + protected static class RoutingDatasourceConfig { + + @Bean + AbstractRoutingDataSource routingDataSource() { + return mock(AbstractRoutingDataSource.class); + } + + } + + @Configuration + protected static class CustomHealthIndicator { + + @Bean + public HealthIndicator customHealthIndicator() { + return () -> Health.down().build(); + } + + } + + @Configuration + @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) + protected static class CassandraConfiguration { + + @Bean + public CassandraOperations cassandraOperations() { + return mock(CassandraOperations.class); + } + + } + + @Configuration + @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) + protected static class CouchbaseConfiguration { + + @Bean + public CouchbaseOperations couchbaseOperations() { + return mock(CouchbaseOperations.class); + } + + } + + @Configuration + protected static class JestClientConfiguration { + + @Bean + public JestClient jestClient() { + return mock(JestClient.class); + } + + } + + @Configuration + @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) + protected static class LdapConfiguration { + + @Bean + public LdapOperations ldapOperations() { + return mock(LdapOperations.class); + } + + } + + @Configuration + @AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) + protected static class Neo4jConfiguration { + + @Bean + public SessionFactory sessionFactory() { + return mock(SessionFactory.class); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthIndicatorsConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthIndicatorsConfigurationTests.java new file mode 100644 index 0000000000..d25c969f57 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthIndicatorsConfigurationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2017 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.autoconfigure.health; + +import org.assertj.core.api.Condition; +import org.junit.Test; + +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.RedisReactiveHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactiveHealthIndicatorsConfiguration}. + * + * @author Stephane Nicoll + */ +public class ReactiveHealthIndicatorsConfigurationTests { + + public final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(HealthIndicatorAutoConfiguration.class)); + + @Test + public void redisHealthIndicator() { + this.contextRunner + .withConfiguration(AutoConfigurations.of( + RedisAutoConfiguration.class)) + .withPropertyValues("management.health.diskspace.enabled:false") + .run(hasSingleReactiveHealthIndicator(RedisReactiveHealthIndicator.class)); + } + + private ContextConsumer hasSingleReactiveHealthIndicator( + Class type) { + return (context) -> assertThat(context).getBeans(ReactiveHealthIndicator.class) + .hasSize(1) + .hasValueSatisfying( + new Condition<>((indicator) -> indicator.getClass().equals(type), + "Wrong indicator type")); + } + +} 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 eed17404af..a14c76ea24 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 @@ -21,9 +21,9 @@ import java.util.Map; import org.junit.Test; +import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorFactory; import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.Status; @@ -59,7 +59,7 @@ public class HealthEndpointTests { private HealthIndicator createHealthIndicator( Map healthIndicators) { - return new HealthIndicatorFactory() + return new CompositeHealthIndicatorFactory() .createHealthIndicator(new OrderedHealthAggregator(), healthIndicators); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/StatusEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/StatusEndpointTests.java index 625bcdde5f..0f49987465 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/StatusEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/StatusEndpointTests.java @@ -21,9 +21,9 @@ import java.util.Map; import org.junit.Test; +import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorFactory; import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.Status; @@ -52,7 +52,7 @@ public class StatusEndpointTests { private HealthIndicator createHealthIndicator( Map healthIndicators) { - return new HealthIndicatorFactory() + return new CompositeHealthIndicatorFactory() .createHealthIndicator(new OrderedHealthAggregator(), healthIndicators); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java index a01903c69f..38d0841b33 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/HealthEndpointWebIntegrationTests.java @@ -22,9 +22,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorFactory; import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.context.ConfigurableApplicationContext; @@ -69,7 +69,7 @@ public class HealthEndpointWebIntegrationTests { @Bean public HealthEndpoint healthEndpoint( Map healthIndicators) { - return new HealthEndpoint(new HealthIndicatorFactory().createHealthIndicator( + return new HealthEndpoint(new CompositeHealthIndicatorFactory().createHealthIndicator( new OrderedHealthAggregator(), healthIndicators)); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/StatusEndpointWebIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/StatusEndpointWebIntegrationTests.java index 9ae5145354..7881eb33b8 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/StatusEndpointWebIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/StatusEndpointWebIntegrationTests.java @@ -22,9 +22,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.actuate.endpoint.StatusEndpoint; +import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorFactory; import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.context.ConfigurableApplicationContext; @@ -67,7 +67,7 @@ public class StatusEndpointWebIntegrationTests { @Bean public StatusEndpoint statusEndpoint( Map healthIndicators) { - return new StatusEndpoint(new HealthIndicatorFactory().createHealthIndicator( + return new StatusEndpoint(new CompositeHealthIndicatorFactory().createHealthIndicator( new OrderedHealthAggregator(), healthIndicators)); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorFactoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactoryTests.java similarity index 93% rename from spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorFactoryTests.java rename to spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactoryTests.java index d6a7170db5..5f04057a4a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorFactoryTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactoryTests.java @@ -24,13 +24,13 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link HealthIndicatorFactory}. + * Tests for {@link CompositeHealthIndicatorFactory}. * * @author Phillip Webb * @author Christian Dupuis * @author Andy Wilkinson */ -public class HealthIndicatorFactoryTests { +public class CompositeHealthIndicatorFactoryTests { @Test public void upAndUpIsAggregatedToUp() throws Exception { @@ -62,7 +62,7 @@ public class HealthIndicatorFactoryTests { private HealthIndicator createHealthIndicator( Map healthIndicators) { - return new HealthIndicatorFactory() + return new CompositeHealthIndicatorFactory() .createHealthIndicator(new OrderedHealthAggregator(), healthIndicators); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorFactoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorFactoryTests.java new file mode 100644 index 0000000000..2ea4e763c6 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorFactoryTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2017 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.Map; + +import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link CompositeReactiveHealthIndicatorFactory}. + * + * @author Stephane Nicoll + */ +public class CompositeReactiveHealthIndicatorFactoryTests { + + private static final Health UP = new Health.Builder().status(Status.UP).build(); + + private static final Health DOWN = new Health.Builder().status(Status.DOWN).build(); + + @Test + public void noHealthIndicator() { + ReactiveHealthIndicator healthIndicator = createHealthIndicator( + Collections.singletonMap("test", () -> Mono.just(UP)), null); + StepVerifier.create(healthIndicator.health()).consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("test"); + }).verifyComplete(); + } + + @Test + public void defaultHealthIndicatorNameFactory() { + ReactiveHealthIndicator healthIndicator = new CompositeReactiveHealthIndicatorFactory() + .createReactiveHealthIndicator(new OrderedHealthAggregator(), + Collections.singletonMap("myHealthIndicator", () -> Mono.just(UP)), + null); + StepVerifier.create(healthIndicator.health()).consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("my"); + }).verifyComplete(); + } + + @Test + public void healthIndicatorIsAdapted() { + ReactiveHealthIndicator healthIndicator = createHealthIndicator( + Collections.singletonMap("test", () -> Mono.just(UP)), + Collections.singletonMap("regular", () -> DOWN)); + StepVerifier.create(healthIndicator.health()).consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.DOWN); + assertThat(h.getDetails()).containsOnlyKeys("test", "regular"); + }).verifyComplete(); + } + + @Test + public void reactiveHealthIndicatorTakesPrecedence() { + ReactiveHealthIndicator reactivehealthIndicator = mock(ReactiveHealthIndicator.class); + given(reactivehealthIndicator.health()).willReturn(Mono.just(UP)); + HealthIndicator regularHealthIndicator = mock(HealthIndicator.class); + given(regularHealthIndicator.health()).willReturn(UP); + ReactiveHealthIndicator healthIndicator = createHealthIndicator( + Collections.singletonMap("test", reactivehealthIndicator), + Collections.singletonMap("test", regularHealthIndicator)); + StepVerifier.create(healthIndicator.health()).consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("test"); + }).verifyComplete(); + verify(reactivehealthIndicator, times(1)).health(); + verify(regularHealthIndicator, times(0)).health(); + } + + private ReactiveHealthIndicator createHealthIndicator( + Map reactiveHealthIndicators, + Map healthIndicators) { + return new CompositeReactiveHealthIndicatorFactory(n -> n) + .createReactiveHealthIndicator(new OrderedHealthAggregator(), + reactiveHealthIndicators, healthIndicators); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java new file mode 100644 index 0000000000..5fac3aff58 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2017 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.time.Duration; + +import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CompositeReactiveHealthIndicator}. + * + * @author Stephane Nicoll + */ +public class CompositeReactiveHealthIndicatorTests { + + private static final Health UNKNOWN_HEALTH = Health.unknown() + .withDetail("detail", "value").build(); + + private static final Health HEALTHY = Health.up().build(); + + private OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator(); + + private CompositeReactiveHealthIndicator indicator = + new CompositeReactiveHealthIndicator(this.healthAggregator); + + @Test + public void singleIndicator() { + this.indicator.addHealthIndicator("test", () -> Mono.just(HEALTHY)); + StepVerifier.create(this.indicator.health()).consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("test"); + assertThat(h.getDetails().get("test")).isEqualTo(HEALTHY); + }).verifyComplete(); + } + + @Test + public void longHealth() { + for (int i = 0; i < 50; i++) { + this.indicator.addHealthIndicator( + "test" + i, new TimeoutHealth(10000, Status.UP)); + } + StepVerifier.withVirtualTime(this.indicator::health) + .expectSubscription() + .thenAwait(Duration.ofMillis(10000)) + .consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).hasSize(50); + }) + .verifyComplete(); + + } + + @Test + public void timeoutReachedUsesFallback() { + this.indicator.addHealthIndicator("slow", new TimeoutHealth(10000, Status.UP)) + .addHealthIndicator("fast", new TimeoutHealth(10, Status.UP)) + .timeoutStrategy(100, UNKNOWN_HEALTH); + StepVerifier.create(this.indicator.health()).consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("slow", "fast"); + assertThat(h.getDetails().get("slow")).isEqualTo(UNKNOWN_HEALTH); + assertThat(h.getDetails().get("fast")).isEqualTo(HEALTHY); + }).verifyComplete(); + } + + @Test + public void timeoutNotReached() { + this.indicator.addHealthIndicator("slow", new TimeoutHealth(10000, Status.UP)) + .addHealthIndicator("fast", new TimeoutHealth(10, Status.UP)) + .timeoutStrategy(20000, null); + StepVerifier.withVirtualTime(this.indicator::health) + .expectSubscription() + .thenAwait(Duration.ofMillis(10000)) + .consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("slow", "fast"); + assertThat(h.getDetails().get("slow")).isEqualTo(HEALTHY); + assertThat(h.getDetails().get("fast")).isEqualTo(HEALTHY); + }) + .verifyComplete(); + } + + static class TimeoutHealth implements ReactiveHealthIndicator { + + private final long timeout; + + private final Status status; + + TimeoutHealth(long timeout, Status status) { + this.timeout = timeout; + this.status = status; + } + + @Override + public Mono health() { + return Mono.delay(Duration.ofMillis(this.timeout)) + .map(l -> Health.status(this.status).build()); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java new file mode 100644 index 0000000000..59ba21ddf9 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2017 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.junit.Test; +import reactor.test.StepVerifier; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HealthIndicatorReactiveAdapter}. + * + * @author Stephane Nicoll + */ +public class HealthIndicatorReactiveAdapterTests { + + @Test + public void delegateReturnsHealth() { + HealthIndicator delegate = mock(HealthIndicator.class); + HealthIndicatorReactiveAdapter adapter = new HealthIndicatorReactiveAdapter(delegate); + Health status = Health.up().build(); + given(delegate.health()).willReturn(status); + StepVerifier.create(adapter.health()).expectNext(status).verifyComplete(); + } + + @Test + public void delegateThrowError() { + HealthIndicator delegate = mock(HealthIndicator.class); + HealthIndicatorReactiveAdapter adapter = new HealthIndicatorReactiveAdapter(delegate); + given(delegate.health()).willThrow(new IllegalStateException("Expected")); + StepVerifier.create(adapter.health()).expectError(IllegalStateException.class); + } + + @Test + public void delegateRunsOnTheElasticScheduler() { + String currentThread = Thread.currentThread().getName(); + HealthIndicator delegate = () -> Health.status(Thread.currentThread().getName() + .equals(currentThread) ? Status.DOWN : Status.UP).build(); + HealthIndicatorReactiveAdapter adapter = new HealthIndicatorReactiveAdapter(delegate); + StepVerifier.create(adapter.health()).expectNext(Health.status(Status.UP).build()) + .verifyComplete(); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java index 40d56eaab7..ed40299df3 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java @@ -22,11 +22,6 @@ import java.util.Properties; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.data.redis.RedisConnectionFailureException; import org.springframework.data.redis.connection.ClusterInfo; import org.springframework.data.redis.connection.RedisClusterConnection; @@ -49,18 +44,6 @@ import static org.mockito.Mockito.verify; */ public class RedisHealthIndicatorTests { - @Test - public void indicatorExists() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, - EndpointAutoConfiguration.class, - HealthIndicatorAutoConfiguration.class)) - .run((context) -> { - assertThat(context).hasSingleBean(RedisConnectionFactory.class); - assertThat(context).hasSingleBean(RedisHealthIndicator.class); - }); - } - @Test public void redisIsUp() throws Exception { Properties info = new Properties(); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisReactiveHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisReactiveHealthIndicatorTests.java new file mode 100644 index 0000000000..79f0f970a7 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisReactiveHealthIndicatorTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2017 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.Properties; + +import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.data.redis.RedisConnectionFailureException; +import org.springframework.data.redis.connection.ReactiveRedisConnection; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.connection.ReactiveServerCommands; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link RedisReactiveHealthIndicator}. + * + * @author Stephane Nicoll + */ +public class RedisReactiveHealthIndicatorTests { + + @Test + public void redisIsUp() throws Exception { + Properties info = new Properties(); + info.put("redis_version", "2.8.9"); + ReactiveServerCommands commands = mock(ReactiveServerCommands.class); + given(commands.info()).willReturn(Mono.just(info)); + RedisReactiveHealthIndicator healthIndicator = createHealthIndicator(commands); + Mono health = healthIndicator.health(); + StepVerifier.create(health).consumeNextWith(h -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("version"); + assertThat(h.getDetails().get("version")).isEqualTo("2.8.9"); + }).verifyComplete(); + } + + @Test + public void redisIsDown() throws Exception { + ReactiveServerCommands commands = mock(ReactiveServerCommands.class); + given(commands.info()).willReturn(Mono.error( + new RedisConnectionFailureException("Connection failed"))); + RedisReactiveHealthIndicator healthIndicator = createHealthIndicator(commands); + Mono health = healthIndicator.health(); + StepVerifier.create(health) + .expectError(RedisConnectionFailureException.class); + } + + private RedisReactiveHealthIndicator createHealthIndicator( + ReactiveServerCommands serverCommands) { + ReactiveRedisConnection redisConnection = mock(ReactiveRedisConnection.class); + ReactiveRedisConnectionFactory redisConnectionFactory = mock( + ReactiveRedisConnectionFactory.class); + given(redisConnectionFactory.getReactiveConnection()).willReturn(redisConnection); + given(redisConnection.serverCommands()).willReturn(serverCommands); + return new RedisReactiveHealthIndicator(redisConnectionFactory); + } + +}