Rework HealthEndpoint and HealthIndicator

With this commit the state of a component or subsystem becomes a first-class citizen in Boot's application health support. HealthIndicators now return a Health instance with status and some contextual details.

An aggregation strategy has been introduced to aggregate several Health instances into one final application Health instance. Out of the box OrderedHealthAggregator can be configured to allow different ordering or a custom HealthAggregator bean can be registered.
pull/931/merge
Christian Dupuis 11 years ago
parent d59cbc830a
commit 4648188782

@ -37,6 +37,8 @@ import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint;
import org.springframework.boot.actuate.endpoint.VanillaPublicMetrics; import org.springframework.boot.actuate.endpoint.VanillaPublicMetrics;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
@ -74,7 +76,10 @@ public class EndpointAutoConfiguration {
private InfoPropertiesConfiguration properties; private InfoPropertiesConfiguration properties;
@Autowired(required = false) @Autowired(required = false)
Map<String, HealthIndicator<? extends Object>> healthIndicators = new HashMap<String, HealthIndicator<? extends Object>>(); private HealthAggregator healthAggregator = new OrderedHealthAggregator();
@Autowired(required = false)
Map<String, HealthIndicator> healthIndicators = new HashMap<String, HealthIndicator>();
@Autowired(required = false) @Autowired(required = false)
private MetricReader metricRepository = new InMemoryMetricRepository(); private MetricReader metricRepository = new InMemoryMetricRepository();
@ -97,7 +102,7 @@ public class EndpointAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public HealthEndpoint healthEndpoint() { public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(this.healthIndicators); return new HealthEndpoint(this.healthAggregator, this.healthIndicators);
} }
@Bean @Bean

@ -16,15 +16,19 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.MongoHealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.RabbitHealthIndicator; import org.springframework.boot.actuate.health.RabbitHealthIndicator;
import org.springframework.boot.actuate.health.RedisHealthIndicator; import org.springframework.boot.actuate.health.RedisHealthIndicator;
import org.springframework.boot.actuate.health.SimpleDataSourceHealthIndicator; import org.springframework.boot.actuate.health.SimpleDataSourceHealthIndicator;
@ -58,9 +62,22 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
RabbitAutoConfiguration.class }) RabbitAutoConfiguration.class })
public class HealthIndicatorAutoConfiguration { public class HealthIndicatorAutoConfiguration {
@Value("${health.status.order:}")
private List<String> statusOrder = null;
@Bean
@ConditionalOnMissingBean
public HealthAggregator healthAggregator() {
OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator();
if (this.statusOrder != null) {
healthAggregator.setStatusOrder(this.statusOrder);
}
return healthAggregator;
}
@Bean @Bean
@ConditionalOnMissingBean(HealthIndicator.class) @ConditionalOnMissingBean(HealthIndicator.class)
public HealthIndicator<?> statusHealthIndicator() { public HealthIndicator statusHealthIndicator() {
return new VanillaHealthIndicator(); return new VanillaHealthIndicator();
} }
@ -69,18 +86,22 @@ public class HealthIndicatorAutoConfiguration {
@ConditionalOnExpression("${health.db.enabled:true}") @ConditionalOnExpression("${health.db.enabled:true}")
public static class DataSourcesHealthIndicatorConfiguration { public static class DataSourcesHealthIndicatorConfiguration {
@Autowired
private HealthAggregator healthAggregator;
@Autowired(required = false) @Autowired(required = false)
private Map<String, DataSource> dataSources; private Map<String, DataSource> dataSources;
@Bean @Bean
@ConditionalOnMissingBean(name = "dbHealthIndicator") @ConditionalOnMissingBean(name = "dbHealthIndicator")
public HealthIndicator<? extends Object> dbHealthIndicator() { public HealthIndicator dbHealthIndicator() {
if (this.dataSources.size() == 1) { if (this.dataSources.size() == 1) {
return new SimpleDataSourceHealthIndicator(this.dataSources.values() return new SimpleDataSourceHealthIndicator(this.dataSources.values()
.iterator().next()); .iterator().next());
} }
CompositeHealthIndicator composite = new CompositeHealthIndicator(); CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
for (Map.Entry<String, DataSource> entry : this.dataSources.entrySet()) { for (Map.Entry<String, DataSource> entry : this.dataSources.entrySet()) {
composite.addHealthIndicator(entry.getKey(), composite.addHealthIndicator(entry.getKey(),
new SimpleDataSourceHealthIndicator(entry.getValue())); new SimpleDataSourceHealthIndicator(entry.getValue()));
@ -94,18 +115,22 @@ public class HealthIndicatorAutoConfiguration {
@ConditionalOnExpression("${health.mongo.enabled:true}") @ConditionalOnExpression("${health.mongo.enabled:true}")
public static class MongoHealthIndicatorConfiguration { public static class MongoHealthIndicatorConfiguration {
@Autowired
private HealthAggregator healthAggregator;
@Autowired @Autowired
private Map<String, MongoTemplate> mongoTemplates; private Map<String, MongoTemplate> mongoTemplates;
@Bean @Bean
@ConditionalOnMissingBean(name = "mongoHealthIndicator") @ConditionalOnMissingBean(name = "mongoHealthIndicator")
public HealthIndicator<?> mongoHealthIndicator() { public HealthIndicator mongoHealthIndicator() {
if (this.mongoTemplates.size() == 1) { if (this.mongoTemplates.size() == 1) {
return new MongoHealthIndicator(this.mongoTemplates.values().iterator() return new MongoHealthIndicator(this.mongoTemplates.values().iterator()
.next()); .next());
} }
CompositeHealthIndicator composite = new CompositeHealthIndicator(); CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
for (Map.Entry<String, MongoTemplate> entry : this.mongoTemplates.entrySet()) { for (Map.Entry<String, MongoTemplate> entry : this.mongoTemplates.entrySet()) {
composite.addHealthIndicator(entry.getKey(), new MongoHealthIndicator( composite.addHealthIndicator(entry.getKey(), new MongoHealthIndicator(
entry.getValue())); entry.getValue()));
@ -119,18 +144,22 @@ public class HealthIndicatorAutoConfiguration {
@ConditionalOnExpression("${health.redis.enabled:true}") @ConditionalOnExpression("${health.redis.enabled:true}")
public static class RedisHealthIndicatorConfiguration { public static class RedisHealthIndicatorConfiguration {
@Autowired
private HealthAggregator healthAggregator;
@Autowired @Autowired
private Map<String, RedisConnectionFactory> redisConnectionFactories; private Map<String, RedisConnectionFactory> redisConnectionFactories;
@Bean @Bean
@ConditionalOnMissingBean(name = "redisHealthIndicator") @ConditionalOnMissingBean(name = "redisHealthIndicator")
public HealthIndicator<?> redisHealthIndicator() { public HealthIndicator redisHealthIndicator() {
if (this.redisConnectionFactories.size() == 1) { if (this.redisConnectionFactories.size() == 1) {
return new RedisHealthIndicator(this.redisConnectionFactories.values() return new RedisHealthIndicator(this.redisConnectionFactories.values()
.iterator().next()); .iterator().next());
} }
CompositeHealthIndicator composite = new CompositeHealthIndicator(); CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
for (Map.Entry<String, RedisConnectionFactory> entry : this.redisConnectionFactories for (Map.Entry<String, RedisConnectionFactory> entry : this.redisConnectionFactories
.entrySet()) { .entrySet()) {
composite.addHealthIndicator(entry.getKey(), new RedisHealthIndicator( composite.addHealthIndicator(entry.getKey(), new RedisHealthIndicator(
@ -145,18 +174,22 @@ public class HealthIndicatorAutoConfiguration {
@ConditionalOnExpression("${health.rabbit.enabled:true}") @ConditionalOnExpression("${health.rabbit.enabled:true}")
public static class RabbitHealthIndicatorConfiguration { public static class RabbitHealthIndicatorConfiguration {
@Autowired
private HealthAggregator healthAggregator;
@Autowired @Autowired
private Map<String, RabbitTemplate> rabbitTemplates; private Map<String, RabbitTemplate> rabbitTemplates;
@Bean @Bean
@ConditionalOnMissingBean(name = "rabbitHealthIndicator") @ConditionalOnMissingBean(name = "rabbitHealthIndicator")
public HealthIndicator<?> rabbitHealthIndicator() { public HealthIndicator rabbitHealthIndicator() {
if (this.rabbitTemplates.size() == 1) { if (this.rabbitTemplates.size() == 1) {
return new RabbitHealthIndicator(this.rabbitTemplates.values().iterator() return new RabbitHealthIndicator(this.rabbitTemplates.values().iterator()
.next()); .next());
} }
CompositeHealthIndicator composite = new CompositeHealthIndicator(); CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
for (Map.Entry<String, RabbitTemplate> entry : this.rabbitTemplates for (Map.Entry<String, RabbitTemplate> entry : this.rabbitTemplates
.entrySet()) { .entrySet()) {
composite.addHealthIndicator(entry.getKey(), new RabbitHealthIndicator( composite.addHealthIndicator(entry.getKey(), new RabbitHealthIndicator(

@ -16,10 +16,11 @@
package org.springframework.boot.actuate.endpoint; package org.springframework.boot.actuate.endpoint;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -31,29 +32,39 @@ import org.springframework.util.Assert;
* @author Christian Dupuis * @author Christian Dupuis
*/ */
@ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = false) @ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = false)
public class HealthEndpoint extends AbstractEndpoint<Map<String, Object>> { public class HealthEndpoint extends AbstractEndpoint<Health> {
private final Map<String, HealthIndicator<? extends Object>> healthIndicators; private final HealthIndicator healthIndicator;
/** /**
* Create a new {@link HealthIndicator} instance. * Create a new {@link HealthIndicator} instance.
*/ */
public HealthEndpoint(Map<String, HealthIndicator<? extends Object>> healthIndicators) { public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
super("health", false, true); super("health", false, true);
Assert.notNull(healthIndicators, "HealthIndicator must not be null");
this.healthIndicators = healthIndicators; Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
if (healthIndicators.size() == 1) {
this.healthIndicator = healthIndicators.values().iterator().next();
}
else {
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
healthAggregator);
for (Map.Entry<String, HealthIndicator> h : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(getKey(h.getKey()), h.getValue());
}
this.healthIndicator = healthIndicator;
}
} }
/** /**
* Invoke all {@link HealthIndicator} delegates and collect their health information. * Invoke all {@link HealthIndicator} delegates and collect their health information.
*/ */
@Override @Override
public Map<String, Object> invoke() { public Health invoke() {
Map<String, Object> health = new LinkedHashMap<String, Object>(); return this.healthIndicator.health();
for (Entry<String, HealthIndicator<?>> entry : this.healthIndicators.entrySet()) {
health.put(getKey(entry.getKey()), entry.getValue().health());
}
return health;
} }
/** /**

@ -19,22 +19,27 @@ package org.springframework.boot.actuate.health;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.util.Assert;
/** /**
* {@link HealthIndicator} that returns health indications from all registered delegates. * {@link HealthIndicator} that returns health indications from all registered delegates.
* *
* @author Tyler J. Frederick * @author Tyler J. Frederick
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis
* @since 1.1.0 * @since 1.1.0
*/ */
public class CompositeHealthIndicator implements HealthIndicator<Map<String, Object>> { public class CompositeHealthIndicator implements HealthIndicator {
private final Map<String, HealthIndicator> indicators;
private final Map<String, HealthIndicator<?>> indicators; private final HealthAggregator healthAggregator;
/** /**
* Create a new {@link CompositeHealthIndicator}. * Create a new {@link CompositeHealthIndicator}.
*/ */
public CompositeHealthIndicator() { public CompositeHealthIndicator(HealthAggregator healthAggregator) {
this.indicators = new LinkedHashMap<String, HealthIndicator<?>>(); this(healthAggregator, new LinkedHashMap<String, HealthIndicator>());
} }
/** /**
@ -42,21 +47,25 @@ public class CompositeHealthIndicator implements HealthIndicator<Map<String, Obj
* @param indicators a map of {@link HealthIndicator}s with the key being used as an * @param indicators a map of {@link HealthIndicator}s with the key being used as an
* indicator name. * indicator name.
*/ */
public CompositeHealthIndicator(Map<String, HealthIndicator<?>> indicators) { public CompositeHealthIndicator(HealthAggregator healthAggregator,
this.indicators = new LinkedHashMap<String, HealthIndicator<?>>(indicators); Map<String, HealthIndicator> indicators) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthAggregator, "Indicators must not be null");
this.indicators = new LinkedHashMap<String, HealthIndicator>(indicators);
this.healthAggregator = healthAggregator;
} }
public void addHealthIndicator(String name, HealthIndicator<?> indicator) { public void addHealthIndicator(String name, HealthIndicator indicator) {
this.indicators.put(name, indicator); this.indicators.put(name, indicator);
} }
@Override @Override
public Map<String, Object> health() { public Health health() {
Map<String, Object> health = new LinkedHashMap<String, Object>(); Map<String, Health> healths = new LinkedHashMap<String, Health>();
for (Map.Entry<String, HealthIndicator<?>> entry : this.indicators.entrySet()) { for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) {
health.put(entry.getKey(), entry.getValue().health()); healths.put(entry.getKey(), entry.getValue().health());
} }
return health; return this.healthAggregator.aggregate(healths);
} }
} }

@ -0,0 +1,133 @@
/*
* Copyright 2012-2014 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 org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
/**
* Value object used to carry information about the health information of a component or
* subsystem.
*
* <p>
* {@link Health} contains a {@link Status} to express the state of a component or
* subsystem and some additional details to carry some contextual information.
*
* <p>
* {@link Health} has a fluent API to make it easy to construct instances. Typical usage
* in a {@link HealthIndicator} would be:
*
* <code>
* Health health = new Health();
* try {
* // do some test to determine state of component
*
* health.up().withDetail("version", "1.1.2");
* }
* catch (Exception ex) {
* health.down().withException(ex);
* }
* return health;
* </code>
*
* @author Christian Dupuis
* @since 1.1.0
*/
@JsonInclude(Include.NON_EMPTY)
public class Health {
private Status status;
private Map<String, Object> details;
public Health() {
this(Status.UNKOWN);
}
public Health(Status status) {
this.status = status;
this.details = new LinkedHashMap<String, Object>();
}
public Health status(Status status) {
Assert.notNull(status, "Status must not be null");
this.status = status;
return this;
}
public Health up() {
return status(Status.UP);
}
public Health down() {
return status(Status.DOWN);
}
public Health withException(Exception ex) {
Assert.notNull(ex, "Exception must not be null");
return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage());
}
@JsonAnySetter
public Health withDetail(String key, Object data) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(data, "Data must not be null");
this.details.put(key, data);
return this;
}
@JsonUnwrapped
public Status getStatus() {
return this.status;
}
@JsonAnyGetter
public Map<String, Object> getDetails() {
return this.details;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj != null && obj instanceof Health) {
return ObjectUtils.nullSafeEquals(this.status, ((Health) obj).status)
&& ObjectUtils.nullSafeEquals(this.details, ((Health) obj).details);
}
return false;
}
@Override
public int hashCode() {
int hashCode = 0;
if (this.status != null) {
hashCode = this.status.hashCode();
}
return 13 * hashCode + this.details.hashCode();
}
}

@ -0,0 +1,45 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.health;
import java.util.Map;
/**
* Strategy interface used by {@link CompositeHealthIndicator} to aggregate {@link Health}
* instances into a final one.
*
* <p>
* This is especially useful to combine subsystem states expressed through
* {@link Health#getStatus()} into one state for the entire system. The default
* implementation {@link OrderedHealthAggregator} sorts {@link Status} instances based on
* a priority list.
*
* <p>
* It is possible to add more complex {@link Status} types to the system. In that case
* either the {@link OrderedHealthAggregator} needs to be properly configured or users
* need to register a custom {@link HealthAggregator} as bean.
*
* @author Christian Dupuis
* @since 1.1.0
*/
public interface HealthAggregator {
/**
* Aggregate several given {@link Health} instances into one.
*/
Health aggregate(Map<String, Health> healths);
}

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2013 the original author or authors. * Copyright 2012-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,11 +22,11 @@ package org.springframework.boot.actuate.health;
* @author Dave Syer * @author Dave Syer
* @see VanillaHealthIndicator * @see VanillaHealthIndicator
*/ */
public interface HealthIndicator<T> { public interface HealthIndicator {
/** /**
* @return an indication of health * @return an indication of health
*/ */
T health(); Health health();
} }

@ -16,9 +16,6 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -31,7 +28,7 @@ import com.mongodb.CommandResult;
* @author Christian Dupuis * @author Christian Dupuis
* @since 1.1.0 * @since 1.1.0
*/ */
public class MongoHealthIndicator implements HealthIndicator<Map<String, Object>> { public class MongoHealthIndicator implements HealthIndicator {
private final MongoTemplate mongoTemplate; private final MongoTemplate mongoTemplate;
@ -41,17 +38,15 @@ public class MongoHealthIndicator implements HealthIndicator<Map<String, Object>
} }
@Override @Override
public Map<String, Object> health() { public Health health() {
Map<String, Object> health = new HashMap<String, Object>(); Health health = new Health();
try { try {
CommandResult result = this.mongoTemplate CommandResult result = this.mongoTemplate
.executeCommand("{ serverStatus: 1 }"); .executeCommand("{ serverStatus: 1 }");
health.put("status", "ok"); health.up().withDetail("version", result.getString("version"));
health.put("version", result.getString("version"));
} }
catch (Exception ex) { catch (Exception ex) {
health.put("status", "error"); health.down().withException(ex);
health.put("error", ex.getClass().getName() + ": " + ex.getMessage());
} }
return health; return health;
} }

@ -0,0 +1,78 @@
/*
* Copyright 2012-2014 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.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Default {@link HealthAggregator} implementation that aggregates {@link Health}
* instances and determines the final system state based on a simple ordered list.
*
* <p>
* If a different order is required or a new {@link Status} type will be used, the order
* can be set by calling {@link #setStatusOrder(List)}.
*
* @author Christian Dupuis
* @since 1.1.0
*/
public class OrderedHealthAggregator implements HealthAggregator {
private List<String> statusOrder = Arrays.asList("DOWN", "OUT_OF_SERVICE", "UP",
"UNKOWN");
@Override
public Health aggregate(Map<String, Health> healths) {
Health health = new Health();
List<Status> status = new ArrayList<Status>();
for (Map.Entry<String, Health> h : healths.entrySet()) {
health.withDetail(h.getKey(), h.getValue());
status.add(h.getValue().getStatus());
}
health.status(aggregateStatus(status));
return health;
}
public void setStatusOrder(List<String> statusOrder) {
this.statusOrder = statusOrder;
}
protected Status aggregateStatus(List<Status> status) {
if (status.size() == 0) {
return Status.UNKOWN;
}
status.sort(new Comparator<Status>() {
@Override
public int compare(Status s1, Status s2) {
return Integer.valueOf(
OrderedHealthAggregator.this.statusOrder.indexOf(s1.getStatus())).compareTo(
Integer.valueOf(OrderedHealthAggregator.this.statusOrder.indexOf(s2.getStatus())));
}
});
return status.get(0);
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.amqp.rabbit.core.ChannelCallback; import org.springframework.amqp.rabbit.core.ChannelCallback;
@ -32,7 +31,7 @@ import com.rabbitmq.client.Channel;
* @author Christian Dupuis * @author Christian Dupuis
* @since 1.1.0 * @since 1.1.0
*/ */
public class RabbitHealthIndicator implements HealthIndicator<Map<String, Object>> { public class RabbitHealthIndicator implements HealthIndicator {
private final RabbitTemplate rabbitTemplate; private final RabbitTemplate rabbitTemplate;
@ -42,10 +41,10 @@ public class RabbitHealthIndicator implements HealthIndicator<Map<String, Object
} }
@Override @Override
public Map<String, Object> health() { public Health health() {
Map<String, Object> health = new HashMap<String, Object>(); Health health = new Health();
try { try {
health.put("version", health.up().withDetail("version",
this.rabbitTemplate.execute(new ChannelCallback<String>() { this.rabbitTemplate.execute(new ChannelCallback<String>() {
@Override @Override
@ -55,12 +54,9 @@ public class RabbitHealthIndicator implements HealthIndicator<Map<String, Object
return serverProperties.get("version").toString(); return serverProperties.get("version").toString();
} }
})); }));
health.put("status", "ok");
} }
catch (Exception ex) { catch (Exception ex) {
health.put("status", "error"); health.down().withException(ex);
health.put("error", ex.getClass().getName() + ": " + ex.getMessage());
} }
return health; return health;
} }

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnection;
@ -32,7 +30,7 @@ import org.springframework.util.Assert;
* @author Christian Dupuis * @author Christian Dupuis
* @since 1.1.0 * @since 1.1.0
*/ */
public class RedisHealthIndicator implements HealthIndicator<Map<String, Object>> { public class RedisHealthIndicator implements HealthIndicator {
private final RedisConnectionFactory redisConnectionFactory; private final RedisConnectionFactory redisConnectionFactory;
@ -42,19 +40,17 @@ public class RedisHealthIndicator implements HealthIndicator<Map<String, Object>
} }
@Override @Override
public Map<String, Object> health() { public Health health() {
Map<String, Object> health = new HashMap<String, Object>(); Health health = new Health();
RedisConnection connection = null; RedisConnection connection = null;
try { try {
connection = RedisConnectionUtils.getConnection(this.redisConnectionFactory); connection = RedisConnectionUtils.getConnection(this.redisConnectionFactory);
Properties info = connection.info(); Properties info = connection.info();
health.put("status", "ok"); health.up().withDetail("version", info.getProperty("redis_version"));
health.put("version", info.getProperty("redis_version"));
} }
catch (Exception ex) { catch (Exception ex) {
health.put("status", "error"); health.down().withException(ex);
health.put("error", ex.getClass().getName() + ": " + ex.getMessage());
} }
finally { finally {
RedisConnectionUtils.releaseConnection(connection, RedisConnectionUtils.releaseConnection(connection,

@ -19,7 +19,6 @@ package org.springframework.boot.actuate.health;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -34,6 +33,7 @@ import org.springframework.util.StringUtils;
* attempts a simple database test. * attempts a simple database test.
* *
* @author Dave Syer * @author Dave Syer
* @author Christian Dupuis
*/ */
public class SimpleDataSourceHealthIndicator implements public class SimpleDataSourceHealthIndicator implements
HealthIndicator<Map<String, Object>> { HealthIndicator<Map<String, Object>> {
@ -72,9 +72,10 @@ public class SimpleDataSourceHealthIndicator implements
} }
@Override @Override
public Map<String, Object> health() { public Health health() {
LinkedHashMap<String, Object> health = new LinkedHashMap<String, Object>(); Health health = new Health();
health.put("status", "ok"); health.up();
String product = "unknown"; String product = "unknown";
if (this.dataSource != null) { if (this.dataSource != null) {
try { try {
@ -85,21 +86,19 @@ public class SimpleDataSourceHealthIndicator implements
return connection.getMetaData().getDatabaseProductName(); return connection.getMetaData().getDatabaseProductName();
} }
}); });
health.put("database", product); health.withDetail("database", product);
} }
catch (DataAccessException ex) { catch (DataAccessException ex) {
health.put("status", "error"); health.down().withException(ex);
health.put("error", ex.getClass().getName() + ": " + ex.getMessage());
} }
String query = detectQuery(product); String query = detectQuery(product);
if (StringUtils.hasText(query)) { if (StringUtils.hasText(query)) {
try { try {
health.put("hello", health.withDetail("hello",
this.jdbcTemplate.queryForObject(query, Object.class)); this.jdbcTemplate.queryForObject(query, Object.class));
} }
catch (Exception ex) { catch (Exception ex) {
health.put("status", "error"); health.down().withException(ex);
health.put("error", ex.getClass().getName() + ": " + ex.getMessage());
} }
} }
} }

@ -0,0 +1,100 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.health;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
* Value object to express state of a component or subsystem.
*
* <p>
* Status provides convenient constants for commonly used states like {@link #UP},
* {@link #DOWN} or {@link #OUT_OF_SERVICE}.
*
* <p>
* Custom states can also be created and used throughout the Spring Boot Health subsystem.
*
* @author Christian Dupuis
* @since 1.1.0
*/
@JsonInclude(Include.NON_EMPTY)
public class Status {
/**
* Convenient constant value representing unknown state
*/
public static final Status UNKOWN = new Status("UNKOWN");
/**
* Convenient constant value representing up state
*/
public static final Status UP = new Status("UP");
/**
* Convenient constant value representing down state
*/
public static final Status DOWN = new Status("DOWN");
/**
* Convenient constant value representing out-of-service state
*/
public static final Status OUT_OF_SERVICE = new Status("OUT_OF_SERVICE");
private final String status;
private final String description;
public Status(String code) {
this(code, "");
}
public Status(String code, String description) {
Assert.notNull(code, "Status must not be null");
Assert.notNull(description, "Description must not be null");
this.status = code;
this.description = description;
}
public String getStatus() {
return this.status;
}
@JsonInclude(Include.NON_EMPTY)
public String getDescription() {
return this.description;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj != null && obj instanceof Status) {
return ObjectUtils.nullSafeEquals(this.status, ((Status) obj).status);
}
return false;
}
@Override
public int hashCode() {
return this.status.hashCode();
}
}

@ -16,16 +16,18 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
/** /**
* Default implementation of {@link HealthIndicator} that simply returns {@literal "ok"}. * Default implementation of {@link HealthIndicator} that simply returns {@literal "ok"}.
* *
* @author Dave Syer * @author Dave Syer
* @author Christian Dupuis
*/ */
public class VanillaHealthIndicator implements HealthIndicator<String> { public class VanillaHealthIndicator implements HealthIndicator {
@Override @Override
public String health() { public Health health() {
return "ok"; return new Health(Status.UP);
} }
} }

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import java.util.Map;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -31,6 +29,7 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
@ -47,6 +46,7 @@ import static org.junit.Assert.assertTrue;
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Greg Turnquist * @author Greg Turnquist
* @author Christian Dupuis
*/ */
public class EndpointAutoConfigurationTests { public class EndpointAutoConfigurationTests {
@ -80,7 +80,6 @@ public class EndpointAutoConfigurationTests {
} }
@Test @Test
@SuppressWarnings("unchecked")
public void healthEndpoint() { public void healthEndpoint() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
this.context.register(EmbeddedDataSourceConfiguration.class, this.context.register(EmbeddedDataSourceConfiguration.class,
@ -88,12 +87,9 @@ public class EndpointAutoConfigurationTests {
this.context.refresh(); this.context.refresh();
HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class);
assertNotNull(bean); assertNotNull(bean);
Map<String, Object> result = bean.invoke(); Health result = bean.invoke();
assertNotNull(result); assertNotNull(result);
assertTrue("Wrong result: " + result, assertTrue("Wrong result: " + result, result.getDetails().containsKey("database"));
((Map<String, Object>) result.get("db")).containsKey("status"));
assertTrue("Wrong result: " + result,
((Map<String, Object>) result.get("db")).containsKey("database"));
} }
@Test @Test
@ -104,9 +100,8 @@ public class EndpointAutoConfigurationTests {
this.context.refresh(); this.context.refresh();
HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class);
assertNotNull(bean); assertNotNull(bean);
Map<String, Object> result = bean.invoke(); Health result = bean.invoke();
assertNotNull(result); assertNotNull(result);
assertTrue("Wrong result: " + result, result.containsKey("status"));
} }
@Test @Test

@ -60,7 +60,6 @@ public class HealthIndicatorAutoConfigurationTests {
} }
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void defaultHealthIndicator() { public void defaultHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -73,7 +72,6 @@ public class HealthIndicatorAutoConfigurationTests {
.getClass()); .getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void redisHealthIndicator() { public void redisHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -87,7 +85,6 @@ public class HealthIndicatorAutoConfigurationTests {
.getClass()); .getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void notRedisHealthIndicator() { public void notRedisHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -102,7 +99,6 @@ public class HealthIndicatorAutoConfigurationTests {
.getClass()); .getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void mongoHealthIndicator() { public void mongoHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -116,7 +112,6 @@ public class HealthIndicatorAutoConfigurationTests {
.getClass()); .getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void notMongoHealthIndicator() { public void notMongoHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -131,7 +126,6 @@ public class HealthIndicatorAutoConfigurationTests {
.getClass()); .getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void combinedHealthIndicator() { public void combinedHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -143,7 +137,6 @@ public class HealthIndicatorAutoConfigurationTests {
assertEquals(2, beans.size()); assertEquals(2, beans.size());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void dataSourceHealthIndicator() { public void dataSourceHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -157,7 +150,6 @@ public class HealthIndicatorAutoConfigurationTests {
.next().getClass()); .next().getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void notDataSourceHealthIndicator() { public void notDataSourceHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -172,7 +164,6 @@ public class HealthIndicatorAutoConfigurationTests {
.getClass()); .getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void rabbitHealthIndicator() { public void rabbitHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -186,7 +177,6 @@ public class HealthIndicatorAutoConfigurationTests {
.getClass()); .getClass());
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void notRabbitHealthIndicator() { public void notRabbitHealthIndicator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();

@ -16,11 +16,14 @@
package org.springframework.boot.actuate.endpoint; package org.springframework.boot.actuate.endpoint;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -32,6 +35,7 @@ import static org.junit.Assert.assertThat;
* Tests for {@link HealthEndpoint}. * Tests for {@link HealthEndpoint}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis
*/ */
public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint> { public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint> {
@ -41,9 +45,8 @@ public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint> {
@Test @Test
public void invoke() throws Exception { public void invoke() throws Exception {
Map<String, Object> result = new HashMap<String, Object>(); Status result = new Status("FINE");
result.put("status", "fine"); assertThat(getEndpointBean().invoke().getStatus(), equalTo(result));
assertThat(getEndpointBean().invoke(), equalTo(result));
} }
@Configuration @Configuration
@ -51,18 +54,25 @@ public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint> {
public static class Config { public static class Config {
@Bean @Bean
public HealthEndpoint endpoint(Map<String, HealthIndicator<?>> healthIndicators) { public HealthEndpoint endpoint(HealthAggregator healthAggregator,
return new HealthEndpoint(healthIndicators); Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(healthAggregator, healthIndicators);
} }
@Bean @Bean
public HealthIndicator<String> statusHealthIndicator() { public HealthIndicator statusHealthIndicator() {
return new HealthIndicator<String>() { return new HealthIndicator() {
@Override @Override
public String health() { public Health health() {
return "fine"; return new Health().status(new Status("FINE"));
} }
}; };
} }
@Bean
public HealthAggregator healthAggregator() {
return new OrderedHealthAggregator();
}
} }
} }

@ -20,10 +20,14 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasEntry;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -34,61 +38,96 @@ import static org.mockito.BDDMockito.given;
* *
* @author Tyler J. Frederick * @author Tyler J. Frederick
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis
*/ */
public class CompositeHealthIndicatorTests { public class CompositeHealthIndicatorTests {
private HealthAggregator healthAggregator;
@Mock @Mock
private HealthIndicator<String> one; private HealthIndicator one;
@Mock @Mock
private HealthIndicator<String> two; private HealthIndicator two;
@Mock @Mock
private HealthIndicator<String> three; private HealthIndicator three;
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
given(this.one.health()).willReturn("1"); given(this.one.health()).willReturn(new Health().withDetail("1", "1"));
given(this.two.health()).willReturn("2"); given(this.two.health()).willReturn(new Health().withDetail("2", "2"));
given(this.three.health()).willReturn("3"); given(this.three.health()).willReturn(new Health().withDetail("3", "3"));
this.healthAggregator = new OrderedHealthAggregator();
} }
@Test @Test
public void createWithIndicators() throws Exception { public void createWithIndicators() throws Exception {
Map<String, HealthIndicator<?>> indicators = new HashMap<String, HealthIndicator<?>>(); Map<String, HealthIndicator> indicators = new HashMap<String, HealthIndicator>();
indicators.put("one", this.one); indicators.put("one", this.one);
indicators.put("two", this.two); indicators.put("two", this.two);
CompositeHealthIndicator composite = new CompositeHealthIndicator(indicators); CompositeHealthIndicator composite = new CompositeHealthIndicator(
Map<String, Object> result = composite.health(); this.healthAggregator, indicators);
assertThat(result.size(), equalTo(2)); Health result = composite.health();
assertThat(result, hasEntry("one", (Object) "1")); assertThat(result.getDetails().size(), equalTo(2));
assertThat(result, hasEntry("two", (Object) "2")); assertThat(result.getDetails(),
hasEntry("one", (Object) new Health().withDetail("1", "1")));
assertThat(result.getDetails(),
hasEntry("two", (Object) new Health().withDetail("2", "2")));
} }
@Test @Test
public void createWithIndicatorsAndAdd() throws Exception { public void createWithIndicatorsAndAdd() throws Exception {
Map<String, HealthIndicator<?>> indicators = new HashMap<String, HealthIndicator<?>>(); Map<String, HealthIndicator> indicators = new HashMap<String, HealthIndicator>();
indicators.put("one", this.one); indicators.put("one", this.one);
indicators.put("two", this.two); indicators.put("two", this.two);
CompositeHealthIndicator composite = new CompositeHealthIndicator(indicators); CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator, indicators);
composite.addHealthIndicator("three", this.three); composite.addHealthIndicator("three", this.three);
Map<String, Object> result = composite.health(); Health result = composite.health();
assertThat(result.size(), equalTo(3)); assertThat(result.getDetails().size(), equalTo(3));
assertThat(result, hasEntry("one", (Object) "1")); assertThat(result.getDetails(),
assertThat(result, hasEntry("two", (Object) "2")); hasEntry("one", (Object) new Health().withDetail("1", "1")));
assertThat(result, hasEntry("three", (Object) "3")); assertThat(result.getDetails(),
hasEntry("two", (Object) new Health().withDetail("2", "2")));
assertThat(result.getDetails(),
hasEntry("three", (Object) new Health().withDetail("3", "3")));
} }
@Test @Test
public void createWithoutAndAdd() throws Exception { public void createWithoutAndAdd() throws Exception {
CompositeHealthIndicator composite = new CompositeHealthIndicator(); CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
composite.addHealthIndicator("one", this.one); composite.addHealthIndicator("one", this.one);
composite.addHealthIndicator("two", this.two); composite.addHealthIndicator("two", this.two);
Map<String, Object> result = composite.health(); Health result = composite.health();
assertThat(result.size(), equalTo(2)); assertThat(result.getDetails().size(), equalTo(2));
assertThat(result, hasEntry("one", (Object) "1")); assertThat(result.getDetails(),
assertThat(result, hasEntry("two", (Object) "2")); hasEntry("one", (Object) new Health().withDetail("1", "1")));
assertThat(result.getDetails(),
hasEntry("two", (Object) new Health().withDetail("2", "2")));
}
@Test
@Ignore
public void testSerialization() throws Exception {
Map<String, HealthIndicator> indicators = new HashMap<String, HealthIndicator>();
indicators.put("db1", this.one);
indicators.put("db2", this.two);
CompositeHealthIndicator innerComposite = new CompositeHealthIndicator(
this.healthAggregator, indicators);
CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
composite.addHealthIndicator("db", innerComposite);
Health result = composite.health();
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
String test = mapper.writeValueAsString(result);
System.out.println(test);
} }
} }

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Map;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -73,9 +71,9 @@ public class MongoHealthIndicatorTests {
commandResult); commandResult);
MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate);
Map<String, Object> health = healthIndicator.health(); Health health = healthIndicator.health();
assertEquals("ok", health.get("status")); assertEquals(Status.UP, health.getStatus());
assertEquals("2.6.4", health.get("version")); assertEquals("2.6.4", health.getDetails().get("version"));
Mockito.verify(commandResult).getString("version"); Mockito.verify(commandResult).getString("version");
Mockito.verify(mongoTemplate).executeCommand("{ serverStatus: 1 }"); Mockito.verify(mongoTemplate).executeCommand("{ serverStatus: 1 }");
@ -88,9 +86,10 @@ public class MongoHealthIndicatorTests {
new MongoException("Connection failed")); new MongoException("Connection failed"));
MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate);
Map<String, Object> health = healthIndicator.health(); Health health = healthIndicator.health();
assertEquals("error", health.get("status")); assertEquals(Status.DOWN, health.getStatus());
assertTrue(((String) health.get("error")).contains("Connection failed")); assertTrue(((String) health.getDetails().get("error"))
.contains("Connection failed"));
Mockito.verify(mongoTemplate).executeCommand("{ serverStatus: 1 }"); Mockito.verify(mongoTemplate).executeCommand("{ serverStatus: 1 }");
} }

@ -0,0 +1,77 @@
/*
* Copyright 2012-2014 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.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link OrderedHealthAggregator}.
*
* @author Christian Dupuis
*/
public class OrderedHealthAggregatorTests {
private OrderedHealthAggregator healthAggregator;
@Before
public void setup() {
this.healthAggregator = new OrderedHealthAggregator();
}
@Test
public void testDefaultOrdering() {
Map<String, Health> healths = new HashMap<String, Health>();
healths.put("h1", new Health(Status.DOWN));
healths.put("h2", new Health(Status.UP));
healths.put("h3", new Health(Status.UNKOWN));
healths.put("h4", new Health(Status.OUT_OF_SERVICE));
assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus());
}
@Test
public void testDefaultOrderingWithCustomStatus() {
Map<String, Health> healths = new HashMap<String, Health>();
healths.put("h1", new Health(Status.DOWN));
healths.put("h2", new Health(Status.UP));
healths.put("h3", new Health(Status.UNKOWN));
healths.put("h4", new Health(Status.OUT_OF_SERVICE));
healths.put("h5", new Health(new Status("CUSTOM")));
assertEquals(new Status("CUSTOM"),
this.healthAggregator.aggregate(healths).getStatus());
}
@Test
public void testDefaultOrderingWithCustomStatusAndOrder() {
this.healthAggregator.setStatusOrder(Arrays.asList("DOWN", "OUT_OF_SERVICE",
"UP", "UNKOWN", "CUSTOM"));
Map<String, Health> healths = new HashMap<String, Health>();
healths.put("h1", new Health(Status.DOWN));
healths.put("h2", new Health(Status.UP));
healths.put("h3", new Health(Status.UNKOWN));
healths.put("h4", new Health(Status.OUT_OF_SERVICE));
healths.put("h5", new Health(new Status("CUSTOM")));
assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus());
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import org.junit.After; import org.junit.After;
@ -76,9 +75,9 @@ public class RedisHealthIndicatorTests {
RedisHealthIndicator healthIndicator = new RedisHealthIndicator( RedisHealthIndicator healthIndicator = new RedisHealthIndicator(
redisConnectionFactory); redisConnectionFactory);
Map<String, Object> health = healthIndicator.health(); Health health = healthIndicator.health();
assertEquals("ok", health.get("status")); assertEquals(Status.UP, health.getStatus());
assertEquals("2.8.9", health.get("version")); assertEquals("2.8.9", health.getDetails().get("version"));
Mockito.verify(redisConnectionFactory).getConnection(); Mockito.verify(redisConnectionFactory).getConnection();
Mockito.verify(redisConnection).info(); Mockito.verify(redisConnection).info();
@ -95,9 +94,10 @@ public class RedisHealthIndicatorTests {
RedisHealthIndicator healthIndicator = new RedisHealthIndicator( RedisHealthIndicator healthIndicator = new RedisHealthIndicator(
redisConnectionFactory); redisConnectionFactory);
Map<String, Object> health = healthIndicator.health(); Health health = healthIndicator.health();
assertEquals("error", health.get("status")); assertEquals(Status.DOWN, health.getStatus());
assertTrue(((String) health.get("error")).contains("Connection failed")); assertTrue(((String) health.getDetails().get("error"))
.contains("Connection failed"));
Mockito.verify(redisConnectionFactory).getConnection(); Mockito.verify(redisConnectionFactory).getConnection();
Mockito.verify(redisConnection).info(); Mockito.verify(redisConnection).info();

@ -17,7 +17,6 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.sql.Connection; import java.sql.Connection;
import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -55,9 +54,9 @@ public class SimpleDataSourceHealthIndicatorTests {
@Test @Test
public void database() { public void database() {
this.indicator.setDataSource(this.dataSource); this.indicator.setDataSource(this.dataSource);
Map<String, Object> health = this.indicator.health(); Health health = this.indicator.health();
assertNotNull(health.get("database")); assertNotNull(health.getDetails().get("database"));
assertNotNull(health.get("hello")); assertNotNull(health.getDetails().get("hello"));
} }
@Test @Test
@ -66,20 +65,20 @@ public class SimpleDataSourceHealthIndicatorTests {
new JdbcTemplate(this.dataSource) new JdbcTemplate(this.dataSource)
.execute("CREATE TABLE FOO (id INTEGER IDENTITY PRIMARY KEY)"); .execute("CREATE TABLE FOO (id INTEGER IDENTITY PRIMARY KEY)");
this.indicator.setQuery("SELECT COUNT(*) from FOO"); this.indicator.setQuery("SELECT COUNT(*) from FOO");
Map<String, Object> health = this.indicator.health(); Health health = this.indicator.health();
System.err.println(health); System.err.println(health);
assertNotNull(health.get("database")); assertNotNull(health.getDetails().get("database"));
assertEquals("ok", health.get("status")); assertEquals(Status.UP, health.getStatus());
assertNotNull(health.get("hello")); assertNotNull(health.getDetails().get("hello"));
} }
@Test @Test
public void error() { public void error() {
this.indicator.setDataSource(this.dataSource); this.indicator.setDataSource(this.dataSource);
this.indicator.setQuery("SELECT COUNT(*) from BAR"); this.indicator.setQuery("SELECT COUNT(*) from BAR");
Map<String, Object> health = this.indicator.health(); Health health = this.indicator.health();
assertNotNull(health.get("database")); assertNotNull(health.getDetails().get("database"));
assertEquals("error", health.get("status")); assertEquals(Status.DOWN, health.getStatus());
} }
@Test @Test
@ -90,8 +89,8 @@ public class SimpleDataSourceHealthIndicatorTests {
this.dataSource.getConnection().getMetaData()); this.dataSource.getConnection().getMetaData());
when(dataSource.getConnection()).thenReturn(connection); when(dataSource.getConnection()).thenReturn(connection);
this.indicator.setDataSource(dataSource); this.indicator.setDataSource(dataSource);
Map<String, Object> health = this.indicator.health(); Health health = this.indicator.health();
assertNotNull(health.get("database")); assertNotNull(health.getDetails().get("database"));
verify(connection, times(2)).close(); verify(connection, times(2)).close();
} }

@ -18,8 +18,7 @@ package org.springframework.boot.actuate.health;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/** /**
* Tests for {@link VanillaHealthIndicator}. * Tests for {@link VanillaHealthIndicator}.
@ -31,7 +30,7 @@ public class VanillaHealthIndicatorTests {
@Test @Test
public void ok() throws Exception { public void ok() throws Exception {
VanillaHealthIndicator healthIndicator = new VanillaHealthIndicator(); VanillaHealthIndicator healthIndicator = new VanillaHealthIndicator();
assertThat(healthIndicator.health(), equalTo("ok")); assertEquals(Status.UP, healthIndicator.health().getStatus());
} }
} }

@ -75,7 +75,7 @@ public class SampleActuatorUiApplicationPortTests {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity( ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/health", String.class); "http://localhost:" + this.managementPort + "/health", String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("{\"status\":\"ok\"}", entity.getBody()); assertEquals("{\"status\":\"UP\"}", entity.getBody());
} }
} }

@ -68,6 +68,6 @@ public class EndpointsPropertiesSampleActuatorApplicationTests {
"http://localhost:" + this.port + "/admin/health", String.class); "http://localhost:" + this.port + "/admin/health", String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"status\":\"ok\"")); entity.getBody().contains("\"status\":\"UP\""));
} }
} }

@ -73,7 +73,7 @@ public class ManagementAddressActuatorApplicationTests {
String.class); String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"status\":\"ok\"")); entity.getBody().contains("\"status\":\"UP\""));
} }
} }

@ -82,7 +82,7 @@ public class ManagementPortSampleActuatorApplicationTests {
"http://localhost:" + this.managementPort + "/health", String.class); "http://localhost:" + this.managementPort + "/health", String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"status\":\"ok\"")); entity.getBody().contains("\"status\":\"UP\""));
} }
@Test @Test

@ -131,7 +131,7 @@ public class SampleActuatorApplicationTests {
"http://localhost:" + this.port + "/health", String.class); "http://localhost:" + this.port + "/health", String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"status\":\"ok\"")); entity.getBody().contains("\"status\":\"UP\""));
} }
@Test @Test

Loading…
Cancel
Save