Add support for reactive health indicator

This commit introduces a "ReactiveHealthIndicator" contract that can be
implemented for health checks against a reactive API.

When running in a WebFlux-based web app, the health and status endpoints
transparently use this in a WebFlux-based application and regular
HealthIndicator are executed on the elastic scheduler.

When running in a Servlet-based web app, the endpoints includes and
adapts available ReactiveHealthIndicators automatically

Closes gh-7972
This commit is contained in:
Stephane Nicoll 2017-08-23 14:59:02 +02:00
parent 8df852bf71
commit a274c78fa0
35 changed files with 2422 additions and 924 deletions

View File

@ -303,6 +303,11 @@
<artifactId>reactor-netty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
@ -369,5 +374,10 @@
<artifactId>snakeyaml</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -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> healthAggregator,
ObjectProvider<Map<String, HealthIndicator>> healthIndicators) {
this.healthIndicator = new HealthIndicatorFactory().createHealthIndicator(
Supplier<Map<String, HealthIndicator>> 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<Map<String, HealthIndicator>> allHealthIndicators(
ObjectProvider<Map<String, HealthIndicator>> healthIndicators) {
return () -> healthIndicators.getIfAvailable(Collections::emptyMap);
}
}
@Configuration
@ConditionalOnClass(name = "reactor.core.publisher.Flux")
static class ReactiveHealthIndicatorsSupplierConfiguration {
@Bean
public Supplier<Map<String, HealthIndicator>> allHealthIndicators(
ObjectProvider<Map<String, HealthIndicator>> healthIndicators,
ObjectProvider<Map<String, ReactiveHealthIndicator>> reactiveHealthIndicators) {
return () -> merge(healthIndicators.getIfAvailable(Collections::emptyMap),
reactiveHealthIndicators.getIfAvailable(Collections::emptyMap));
}
private Map<String, HealthIndicator> merge(
Map<String, HealthIndicator> healthIndicators,
Map<String, ReactiveHealthIndicator> reactiveHealthIndicators) {
if (ObjectUtils.isEmpty(reactiveHealthIndicators)) {
return healthIndicators;
}
Map<String, HealthIndicator> 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)

View File

@ -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> healthAggregator,
ObjectProvider<Map<String, ReactiveHealthIndicator>> reactiveHealthIndicators,
ObjectProvider<Map<String, HealthIndicator>> 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);
}
}
}

View File

@ -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

View File

@ -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 <H> the health indicator type
* @param <S> the bean source type
* @author Stephane Nicoll
* @since 2.0.0
*/
public class CompositeReactiveHealthIndicatorConfiguration<H extends ReactiveHealthIndicator, S> {
@Autowired
private HealthAggregator healthAggregator;
protected ReactiveHealthIndicator createHealthIndicator(Map<String, S> beans) {
if (beans.size() == 1) {
return createHealthIndicator(beans.values().iterator().next());
}
CompositeReactiveHealthIndicator composite = new CompositeReactiveHealthIndicator(
this.healthAggregator);
for (Map.Entry<String, S> 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<H> indicatorClass = (Class<H>) generics[0];
Class<S> sourceClass = (Class<S>) generics[1];
try {
return indicatorClass.getConstructor(sourceClass).newInstance(source);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create indicator " + indicatorClass
+ " for source " + sourceClass, ex);
}
}
}

View File

@ -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<CassandraHealthIndicator, CassandraOperations> {
private final Map<String, CassandraOperations> cassandraOperations;
public CassandraHealthIndicatorConfiguration(
Map<String, CassandraOperations> 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<CouchbaseHealthIndicator, CouchbaseOperations> {
private final Map<String, CouchbaseOperations> couchbaseOperations;
public CouchbaseHealthIndicatorConfiguration(
Map<String, CouchbaseOperations> 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<DataSourceHealthIndicator, DataSource>
implements InitializingBean {
private final Map<String, DataSource> dataSources;
private final Collection<DataSourcePoolMetadataProvider> metadataProviders;
private DataSourcePoolMetadataProvider poolMetadataProvider;
public DataSourcesHealthIndicatorConfiguration(
ObjectProvider<Map<String, DataSource>> dataSources,
ObjectProvider<Collection<DataSourcePoolMetadataProvider>> metadataProviders) {
this.dataSources = filterDataSources(dataSources.getIfAvailable());
this.metadataProviders = metadataProviders.getIfAvailable();
}
private Map<String, DataSource> filterDataSources(
Map<String, DataSource> candidates) {
if (candidates == null) {
return null;
}
Map<String, DataSource> dataSources = new LinkedHashMap<>();
for (Map.Entry<String, DataSource> 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<LdapHealthIndicator, LdapOperations> {
private final Map<String, LdapOperations> ldapOperations;
public LdapHealthIndicatorConfiguration(
Map<String, LdapOperations> 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<MongoHealthIndicator, MongoTemplate> {
private final Map<String, MongoTemplate> mongoTemplates;
public MongoHealthIndicatorConfiguration(
Map<String, MongoTemplate> 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<Neo4jHealthIndicator, SessionFactory> {
private final Map<String, SessionFactory> sessionFactories;
public Neo4jHealthIndicatorConfiguration(
Map<String, SessionFactory> 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<RedisHealthIndicator, RedisConnectionFactory> {
private final Map<String, RedisConnectionFactory> redisConnectionFactories;
public RedisHealthIndicatorConfiguration(
Map<String, RedisConnectionFactory> 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<RabbitHealthIndicator, RabbitTemplate> {
private final Map<String, RabbitTemplate> rabbitTemplates;
public RabbitHealthIndicatorConfiguration(
Map<String, RabbitTemplate> 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<SolrHealthIndicator, SolrClient> {
private final Map<String, SolrClient> solrClients;
public SolrHealthIndicatorConfiguration(Map<String, SolrClient> 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<MailHealthIndicator, JavaMailSenderImpl> {
private final Map<String, JavaMailSenderImpl> mailSenders;
public MailHealthIndicatorConfiguration(
ObjectProvider<Map<String, JavaMailSenderImpl>> 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<JmsHealthIndicator, ConnectionFactory> {
private final Map<String, ConnectionFactory> connectionFactories;
public JmsHealthIndicatorConfiguration(
ObjectProvider<Map<String, ConnectionFactory>> connectionFactories) {
this.connectionFactories = connectionFactories.getIfAvailable();
}
@Bean
@ConditionalOnMissingBean(name = "jmsHealthIndicator")
public HealthIndicator jmsHealthIndicator() {
return createHealthIndicator(this.connectionFactories);
}
}
}

View File

@ -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<CassandraHealthIndicator, CassandraOperations> {
private final Map<String, CassandraOperations> cassandraOperations;
public CassandraHealthIndicatorConfiguration(
Map<String, CassandraOperations> 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<CouchbaseHealthIndicator, CouchbaseOperations> {
private final Map<String, CouchbaseOperations> couchbaseOperations;
public CouchbaseHealthIndicatorConfiguration(
Map<String, CouchbaseOperations> 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<DataSourceHealthIndicator, DataSource>
implements InitializingBean {
private final Map<String, DataSource> dataSources;
private final Collection<DataSourcePoolMetadataProvider> metadataProviders;
private DataSourcePoolMetadataProvider poolMetadataProvider;
public DataSourcesHealthIndicatorConfiguration(
ObjectProvider<Map<String, DataSource>> dataSources,
ObjectProvider<Collection<DataSourcePoolMetadataProvider>> metadataProviders) {
this.dataSources = filterDataSources(dataSources.getIfAvailable());
this.metadataProviders = metadataProviders.getIfAvailable();
}
private Map<String, DataSource> filterDataSources(
Map<String, DataSource> candidates) {
if (candidates == null) {
return null;
}
Map<String, DataSource> dataSources = new LinkedHashMap<>();
for (Map.Entry<String, DataSource> 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<LdapHealthIndicator, LdapOperations> {
private final Map<String, LdapOperations> ldapOperations;
public LdapHealthIndicatorConfiguration(
Map<String, LdapOperations> 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<MongoHealthIndicator, MongoTemplate> {
private final Map<String, MongoTemplate> mongoTemplates;
public MongoHealthIndicatorConfiguration(
Map<String, MongoTemplate> 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<Neo4jHealthIndicator, SessionFactory> {
private final Map<String, SessionFactory> sessionFactories;
public Neo4jHealthIndicatorConfiguration(
Map<String, SessionFactory> 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<RedisHealthIndicator, RedisConnectionFactory> {
private final Map<String, RedisConnectionFactory> redisConnectionFactories;
public RedisHealthIndicatorConfiguration(
Map<String, RedisConnectionFactory> 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<RabbitHealthIndicator, RabbitTemplate> {
private final Map<String, RabbitTemplate> rabbitTemplates;
public RabbitHealthIndicatorConfiguration(
Map<String, RabbitTemplate> 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<SolrHealthIndicator, SolrClient> {
private final Map<String, SolrClient> solrClients;
public SolrHealthIndicatorConfiguration(Map<String, SolrClient> 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<MailHealthIndicator, JavaMailSenderImpl> {
private final Map<String, JavaMailSenderImpl> mailSenders;
public MailHealthIndicatorConfiguration(
ObjectProvider<Map<String, JavaMailSenderImpl>> 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<JmsHealthIndicator, ConnectionFactory> {
private final Map<String, ConnectionFactory> connectionFactories;
public JmsHealthIndicatorConfiguration(
ObjectProvider<Map<String, ConnectionFactory>> connectionFactories) {
this.connectionFactories = connectionFactories.getIfAvailable();
}
@Bean
@ConditionalOnMissingBean(name = "jmsHealthIndicator")
public HealthIndicator jmsHealthIndicator() {
return createHealthIndicator(this.connectionFactories);
}
}
}

View File

@ -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<RedisReactiveHealthIndicator,
ReactiveRedisConnectionFactory> {
private final Map<String, ReactiveRedisConnectionFactory> redisConnectionFactories;
RedisHealthIndicatorConfiguration(
Map<String, ReactiveRedisConnectionFactory> redisConnectionFactories) {
this.redisConnectionFactories = redisConnectionFactories;
}
@Bean
@ConditionalOnMissingBean(name = "redisHealthIndicator")
public ReactiveHealthIndicator redisHealthIndicator() {
return createHealthIndicator(this.redisConnectionFactories);
}
}
}

View File

@ -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<WebEndpointResponse<Health>> health() {
return this.delegate.health().map(health -> {
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(health, status);
});
}
}

View File

@ -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<WebEndpointResponse<Health>> health() {
return this.delegate.health().map(health -> {
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(
Health.status(health.getStatus()).build(), status);
});
}
}

View File

@ -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> 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<Health> doHealthCheck(Health.Builder builder);
}

View File

@ -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<String, String> healthIndicatorNameFactory;
public CompositeHealthIndicatorFactory(
Function<String, String> 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<String, HealthIndicator> 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<String, HealthIndicator> 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;
}
}

View File

@ -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<String, ReactiveHealthIndicator> indicators;
private final HealthAggregator healthAggregator;
private Long timeout;
private Health timeoutHealth;
private final Function<Mono<Health>, Mono<Health>> timeoutCompose;
public CompositeReactiveHealthIndicator(HealthAggregator healthAggregator) {
this(healthAggregator, new LinkedHashMap<>());
}
public CompositeReactiveHealthIndicator(HealthAggregator healthAggregator,
Map<String, ReactiveHealthIndicator> 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> 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);
}
}

View File

@ -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<String, String> healthIndicatorNameFactory;
public CompositeReactiveHealthIndicatorFactory(
Function<String, String> 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<String, ReactiveHealthIndicator> reactiveHealthIndicators,
Map<String, HealthIndicator> 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<String, ReactiveHealthIndicator> merge(
Map<String, ReactiveHealthIndicator> reactiveHealthIndicators,
Map<String, HealthIndicator> healthIndicators) {
if (ObjectUtils.isEmpty(healthIndicators)) {
return reactiveHealthIndicators;
}
Map<String, ReactiveHealthIndicator> allIndicators = new LinkedHashMap<>(
reactiveHealthIndicators);
healthIndicators.forEach((beanName, indicator) -> {
String name = this.healthIndicatorNameFactory.apply(beanName);
allIndicators.computeIfAbsent(name, n ->
new HealthIndicatorReactiveAdapter(indicator));
});
return allIndicators;
}
}

View File

@ -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);
}

View File

@ -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<String, String> {
@Override
public String apply(String name) {
int index = name.toLowerCase().indexOf("healthindicator");
if (index > 0) {
return name.substring(0, index);
}
return name;
}
}

View File

@ -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> health() {
return Mono.create((sink) ->
Schedulers.elastic().schedule(() -> invoke(sink)));
}
private void invoke(MonoSink<Health> sink) {
try {
Health health = this.delegate.health();
sink.success(health);
}
catch (Exception ex) {
sink.error(ex);
}
}
}

View File

@ -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.
* <p>
* 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> health();
}

View File

@ -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;

View File

@ -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<Health> doHealthCheck(Health.Builder builder) {
return this.connectionFactory.getReactiveConnection().serverCommands().info()
.map(info -> builder.up().withDetail(
RedisHealthIndicator.VERSION, info.getProperty(
RedisHealthIndicator.REDIS_VERSION)).build());
}
}

View File

@ -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;
}
}
}

View File

@ -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<String, Integer> 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<String, Integer> 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));
}

View File

@ -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<String, HealthIndicator> 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<String, HealthIndicator> 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<String, HealthIndicator> 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<String, HealthIndicator> 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<AssertableApplicationContext> hasSingleHealthIndicator(
Class<? extends HealthIndicator> 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);
}
}
}

View File

@ -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<String, HealthIndicator> 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<String, HealthIndicator> 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<String, HealthIndicator> 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<String, HealthIndicator> 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<AssertableApplicationContext> hasSingleHealthIndicator(
Class<? extends HealthIndicator> 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);
}
}
}

View File

@ -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<AssertableApplicationContext> hasSingleReactiveHealthIndicator(
Class<? extends ReactiveHealthIndicator> type) {
return (context) -> assertThat(context).getBeans(ReactiveHealthIndicator.class)
.hasSize(1)
.hasValueSatisfying(
new Condition<>((indicator) -> indicator.getClass().equals(type),
"Wrong indicator type"));
}
}

View File

@ -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<String, HealthIndicator> healthIndicators) {
return new HealthIndicatorFactory()
return new CompositeHealthIndicatorFactory()
.createHealthIndicator(new OrderedHealthAggregator(), healthIndicators);
}

View File

@ -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<String, HealthIndicator> healthIndicators) {
return new HealthIndicatorFactory()
return new CompositeHealthIndicatorFactory()
.createHealthIndicator(new OrderedHealthAggregator(), healthIndicators);
}

View File

@ -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<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(new HealthIndicatorFactory().createHealthIndicator(
return new HealthEndpoint(new CompositeHealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators));
}

View File

@ -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<String, HealthIndicator> healthIndicators) {
return new StatusEndpoint(new HealthIndicatorFactory().createHealthIndicator(
return new StatusEndpoint(new CompositeHealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators));
}

View File

@ -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<String, HealthIndicator> healthIndicators) {
return new HealthIndicatorFactory()
return new CompositeHealthIndicatorFactory()
.createHealthIndicator(new OrderedHealthAggregator(), healthIndicators);
}

View File

@ -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<String, ReactiveHealthIndicator> reactiveHealthIndicators,
Map<String, HealthIndicator> healthIndicators) {
return new CompositeReactiveHealthIndicatorFactory(n -> n)
.createReactiveHealthIndicator(new OrderedHealthAggregator(),
reactiveHealthIndicators, healthIndicators);
}
}

View File

@ -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> health() {
return Mono.delay(Duration.ofMillis(this.timeout))
.map(l -> Health.status(this.status).build());
}
}
}

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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> 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> 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);
}
}