From 46481887825a85b66161181e5fdb229608a6cca2 Mon Sep 17 00:00:00 2001 From: Christian Dupuis Date: Wed, 21 May 2014 18:26:02 +0200 Subject: [PATCH] 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. --- .../EndpointAutoConfiguration.java | 9 +- .../HealthIndicatorAutoConfiguration.java | 51 +++++-- .../boot/actuate/endpoint/HealthEndpoint.java | 37 +++-- .../health/CompositeHealthIndicator.java | 35 +++-- .../boot/actuate/health/Health.java | 133 ++++++++++++++++++ .../boot/actuate/health/HealthAggregator.java | 45 ++++++ .../boot/actuate/health/HealthIndicator.java | 8 +- .../actuate/health/MongoHealthIndicator.java | 17 +-- .../health/OrderedHealthAggregator.java | 78 ++++++++++ .../actuate/health/RabbitHealthIndicator.java | 16 +-- .../actuate/health/RedisHealthIndicator.java | 16 +-- .../SimpleDataSourceHealthIndicator.java | 21 ++- .../boot/actuate/health/Status.java | 100 +++++++++++++ .../health/VanillaHealthIndicator.java | 10 +- .../EndpointAutoConfigurationTests.java | 17 +-- ...HealthIndicatorAutoConfigurationTests.java | 10 -- .../actuate/endpoint/HealthEndpointTests.java | 30 ++-- .../health/CompositeHealthIndicatorTests.java | 89 ++++++++---- .../health/MongoHealthIndicatorTests.java | 17 ++- .../health/OrderedHealthAggregatorTests.java | 77 ++++++++++ .../health/RedisHealthIndicatorTests.java | 16 +-- .../SimpleDataSourceHealthIndicatorTests.java | 29 ++-- .../health/VanillaHealthIndicatorTests.java | 7 +- .../SampleActuatorUiApplicationPortTests.java | 2 +- ...pertiesSampleActuatorApplicationTests.java | 2 +- ...gementAddressActuatorApplicationTests.java | 2 +- ...entPortSampleActuatorApplicationTests.java | 2 +- .../SampleActuatorApplicationTests.java | 2 +- 28 files changed, 694 insertions(+), 184 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index 680861092e..06ecc4230c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -37,6 +37,8 @@ import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.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.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; @@ -74,7 +76,10 @@ public class EndpointAutoConfiguration { private InfoPropertiesConfiguration properties; @Autowired(required = false) - Map> healthIndicators = new HashMap>(); + private HealthAggregator healthAggregator = new OrderedHealthAggregator(); + + @Autowired(required = false) + Map healthIndicators = new HashMap(); @Autowired(required = false) private MetricReader metricRepository = new InMemoryMetricRepository(); @@ -97,7 +102,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean public HealthEndpoint healthEndpoint() { - return new HealthEndpoint(this.healthIndicators); + return new HealthEndpoint(this.healthAggregator, this.healthIndicators); } @Bean diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index 1c70ad1ef4..55d697d6b9 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -16,15 +16,19 @@ package org.springframework.boot.actuate.autoconfigure; +import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.springframework.amqp.rabbit.core.RabbitTemplate; 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.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; 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.RedisHealthIndicator; import org.springframework.boot.actuate.health.SimpleDataSourceHealthIndicator; @@ -58,9 +62,22 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; RabbitAutoConfiguration.class }) public class HealthIndicatorAutoConfiguration { + @Value("${health.status.order:}") + private List statusOrder = null; + + @Bean + @ConditionalOnMissingBean + public HealthAggregator healthAggregator() { + OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator(); + if (this.statusOrder != null) { + healthAggregator.setStatusOrder(this.statusOrder); + } + return healthAggregator; + } + @Bean @ConditionalOnMissingBean(HealthIndicator.class) - public HealthIndicator statusHealthIndicator() { + public HealthIndicator statusHealthIndicator() { return new VanillaHealthIndicator(); } @@ -69,18 +86,22 @@ public class HealthIndicatorAutoConfiguration { @ConditionalOnExpression("${health.db.enabled:true}") public static class DataSourcesHealthIndicatorConfiguration { + @Autowired + private HealthAggregator healthAggregator; + @Autowired(required = false) private Map dataSources; @Bean @ConditionalOnMissingBean(name = "dbHealthIndicator") - public HealthIndicator dbHealthIndicator() { + public HealthIndicator dbHealthIndicator() { if (this.dataSources.size() == 1) { return new SimpleDataSourceHealthIndicator(this.dataSources.values() .iterator().next()); } - CompositeHealthIndicator composite = new CompositeHealthIndicator(); + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); for (Map.Entry entry : this.dataSources.entrySet()) { composite.addHealthIndicator(entry.getKey(), new SimpleDataSourceHealthIndicator(entry.getValue())); @@ -94,18 +115,22 @@ public class HealthIndicatorAutoConfiguration { @ConditionalOnExpression("${health.mongo.enabled:true}") public static class MongoHealthIndicatorConfiguration { + @Autowired + private HealthAggregator healthAggregator; + @Autowired private Map mongoTemplates; @Bean @ConditionalOnMissingBean(name = "mongoHealthIndicator") - public HealthIndicator mongoHealthIndicator() { + public HealthIndicator mongoHealthIndicator() { if (this.mongoTemplates.size() == 1) { return new MongoHealthIndicator(this.mongoTemplates.values().iterator() .next()); } - CompositeHealthIndicator composite = new CompositeHealthIndicator(); + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); for (Map.Entry entry : this.mongoTemplates.entrySet()) { composite.addHealthIndicator(entry.getKey(), new MongoHealthIndicator( entry.getValue())); @@ -119,18 +144,22 @@ public class HealthIndicatorAutoConfiguration { @ConditionalOnExpression("${health.redis.enabled:true}") public static class RedisHealthIndicatorConfiguration { + @Autowired + private HealthAggregator healthAggregator; + @Autowired private Map redisConnectionFactories; @Bean @ConditionalOnMissingBean(name = "redisHealthIndicator") - public HealthIndicator redisHealthIndicator() { + public HealthIndicator redisHealthIndicator() { if (this.redisConnectionFactories.size() == 1) { return new RedisHealthIndicator(this.redisConnectionFactories.values() .iterator().next()); } - CompositeHealthIndicator composite = new CompositeHealthIndicator(); + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); for (Map.Entry entry : this.redisConnectionFactories .entrySet()) { composite.addHealthIndicator(entry.getKey(), new RedisHealthIndicator( @@ -145,18 +174,22 @@ public class HealthIndicatorAutoConfiguration { @ConditionalOnExpression("${health.rabbit.enabled:true}") public static class RabbitHealthIndicatorConfiguration { + @Autowired + private HealthAggregator healthAggregator; + @Autowired private Map rabbitTemplates; @Bean @ConditionalOnMissingBean(name = "rabbitHealthIndicator") - public HealthIndicator rabbitHealthIndicator() { + public HealthIndicator rabbitHealthIndicator() { if (this.rabbitTemplates.size() == 1) { return new RabbitHealthIndicator(this.rabbitTemplates.values().iterator() .next()); } - CompositeHealthIndicator composite = new CompositeHealthIndicator(); + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); for (Map.Entry entry : this.rabbitTemplates .entrySet()) { composite.addHealthIndicator(entry.getKey(), new RabbitHealthIndicator( diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java index 6f44146b89..ed43df33b4 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java @@ -16,10 +16,11 @@ package org.springframework.boot.actuate.endpoint; -import java.util.LinkedHashMap; 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.context.properties.ConfigurationProperties; import org.springframework.util.Assert; @@ -31,29 +32,39 @@ import org.springframework.util.Assert; * @author Christian Dupuis */ @ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = false) -public class HealthEndpoint extends AbstractEndpoint> { +public class HealthEndpoint extends AbstractEndpoint { - private final Map> healthIndicators; + private final HealthIndicator healthIndicator; /** * Create a new {@link HealthIndicator} instance. */ - public HealthEndpoint(Map> healthIndicators) { + public HealthEndpoint(HealthAggregator healthAggregator, + Map healthIndicators) { 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 h : healthIndicators.entrySet()) { + healthIndicator.addHealthIndicator(getKey(h.getKey()), h.getValue()); + } + this.healthIndicator = healthIndicator; + } } /** * Invoke all {@link HealthIndicator} delegates and collect their health information. */ @Override - public Map invoke() { - Map health = new LinkedHashMap(); - for (Entry> entry : this.healthIndicators.entrySet()) { - health.put(getKey(entry.getKey()), entry.getValue().health()); - } - return health; + public Health invoke() { + return this.healthIndicator.health(); } /** diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java index bc3ffa9c14..4de09d7228 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java @@ -19,22 +19,27 @@ package org.springframework.boot.actuate.health; import java.util.LinkedHashMap; import java.util.Map; +import org.springframework.util.Assert; + /** * {@link HealthIndicator} that returns health indications from all registered delegates. - * + * * @author Tyler J. Frederick * @author Phillip Webb + * @author Christian Dupuis * @since 1.1.0 */ -public class CompositeHealthIndicator implements HealthIndicator> { +public class CompositeHealthIndicator implements HealthIndicator { + + private final Map indicators; - private final Map> indicators; + private final HealthAggregator healthAggregator; /** * Create a new {@link CompositeHealthIndicator}. */ - public CompositeHealthIndicator() { - this.indicators = new LinkedHashMap>(); + public CompositeHealthIndicator(HealthAggregator healthAggregator) { + this(healthAggregator, new LinkedHashMap()); } /** @@ -42,21 +47,25 @@ public class CompositeHealthIndicator implements HealthIndicator> indicators) { - this.indicators = new LinkedHashMap>(indicators); + public CompositeHealthIndicator(HealthAggregator healthAggregator, + Map indicators) { + Assert.notNull(healthAggregator, "HealthAggregator must not be null"); + Assert.notNull(healthAggregator, "Indicators must not be null"); + this.indicators = new LinkedHashMap(indicators); + this.healthAggregator = healthAggregator; } - public void addHealthIndicator(String name, HealthIndicator indicator) { + public void addHealthIndicator(String name, HealthIndicator indicator) { this.indicators.put(name, indicator); } @Override - public Map health() { - Map health = new LinkedHashMap(); - for (Map.Entry> entry : this.indicators.entrySet()) { - health.put(entry.getKey(), entry.getValue().health()); + public Health health() { + Map healths = new LinkedHashMap(); + for (Map.Entry entry : this.indicators.entrySet()) { + healths.put(entry.getKey(), entry.getValue().health()); } - return health; + return this.healthAggregator.aggregate(healths); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java new file mode 100644 index 0000000000..e9540c7c42 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java @@ -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. + * + *

+ * {@link Health} contains a {@link Status} to express the state of a component or + * subsystem and some additional details to carry some contextual information. + * + *

+ * {@link Health} has a fluent API to make it easy to construct instances. Typical usage + * in a {@link HealthIndicator} would be: + * + * + * 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; + * + * + * @author Christian Dupuis + * @since 1.1.0 + */ +@JsonInclude(Include.NON_EMPTY) +public class Health { + + private Status status; + + private Map details; + + public Health() { + this(Status.UNKOWN); + } + + public Health(Status status) { + this.status = status; + this.details = new LinkedHashMap(); + } + + 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 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(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java new file mode 100644 index 0000000000..0a0283d4cc --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java @@ -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. + * + *

+ * 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. + * + *

+ * 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 healths); +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java index 6e82b451a8..a39f572498 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java @@ -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"); * you may not use this file except in compliance with the License. @@ -18,15 +18,15 @@ package org.springframework.boot.actuate.health; /** * Strategy interface used to provide an indication of application health. - * + * * @author Dave Syer * @see VanillaHealthIndicator */ -public interface HealthIndicator { +public interface HealthIndicator { /** * @return an indication of health */ - T health(); + Health health(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java index 1e89500a9c..e6364923fa 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java @@ -16,9 +16,6 @@ package org.springframework.boot.actuate.health; -import java.util.HashMap; -import java.util.Map; - import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.util.Assert; @@ -27,11 +24,11 @@ import com.mongodb.CommandResult; /** * Simple implementation of a {@link HealthIndicator} returning status information for * Mongo data stores. - * + * * @author Christian Dupuis * @since 1.1.0 */ -public class MongoHealthIndicator implements HealthIndicator> { +public class MongoHealthIndicator implements HealthIndicator { private final MongoTemplate mongoTemplate; @@ -41,17 +38,15 @@ public class MongoHealthIndicator implements HealthIndicator } @Override - public Map health() { - Map health = new HashMap(); + public Health health() { + Health health = new Health(); try { CommandResult result = this.mongoTemplate .executeCommand("{ serverStatus: 1 }"); - health.put("status", "ok"); - health.put("version", result.getString("version")); + health.up().withDetail("version", result.getString("version")); } catch (Exception ex) { - health.put("status", "error"); - health.put("error", ex.getClass().getName() + ": " + ex.getMessage()); + health.down().withException(ex); } return health; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java new file mode 100644 index 0000000000..9cdbea476b --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java @@ -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. + * + *

+ * 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 statusOrder = Arrays.asList("DOWN", "OUT_OF_SERVICE", "UP", + "UNKOWN"); + + @Override + public Health aggregate(Map healths) { + Health health = new Health(); + + List status = new ArrayList(); + for (Map.Entry h : healths.entrySet()) { + health.withDetail(h.getKey(), h.getValue()); + status.add(h.getValue().getStatus()); + } + health.status(aggregateStatus(status)); + return health; + } + + public void setStatusOrder(List statusOrder) { + this.statusOrder = statusOrder; + } + + protected Status aggregateStatus(List status) { + + if (status.size() == 0) { + return Status.UNKOWN; + } + + status.sort(new Comparator() { + + @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); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java index bc7f54387e..771d856398 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.health; -import java.util.HashMap; import java.util.Map; import org.springframework.amqp.rabbit.core.ChannelCallback; @@ -28,11 +27,11 @@ import com.rabbitmq.client.Channel; /** * Simple implementation of a {@link HealthIndicator} returning status information for the * RabbitMQ messaging system. - * + * * @author Christian Dupuis * @since 1.1.0 */ -public class RabbitHealthIndicator implements HealthIndicator> { +public class RabbitHealthIndicator implements HealthIndicator { private final RabbitTemplate rabbitTemplate; @@ -42,10 +41,10 @@ public class RabbitHealthIndicator implements HealthIndicator health() { - Map health = new HashMap(); + public Health health() { + Health health = new Health(); try { - health.put("version", + health.up().withDetail("version", this.rabbitTemplate.execute(new ChannelCallback() { @Override @@ -55,12 +54,9 @@ public class RabbitHealthIndicator implements HealthIndicator> { +public class RedisHealthIndicator implements HealthIndicator { private final RedisConnectionFactory redisConnectionFactory; @@ -42,19 +40,17 @@ public class RedisHealthIndicator implements HealthIndicator } @Override - public Map health() { - Map health = new HashMap(); + public Health health() { + Health health = new Health(); RedisConnection connection = null; try { connection = RedisConnectionUtils.getConnection(this.redisConnectionFactory); Properties info = connection.info(); - health.put("status", "ok"); - health.put("version", info.getProperty("redis_version")); + health.up().withDetail("version", info.getProperty("redis_version")); } catch (Exception ex) { - health.put("status", "error"); - health.put("error", ex.getClass().getName() + ": " + ex.getMessage()); + health.down().withException(ex); } finally { RedisConnectionUtils.releaseConnection(connection, diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java index 383ccf7d30..ead2847f33 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java @@ -19,7 +19,6 @@ package org.springframework.boot.actuate.health; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; import javax.sql.DataSource; @@ -32,8 +31,9 @@ import org.springframework.util.StringUtils; /** * Simple implementation of {@link HealthIndicator} that returns a status and also * attempts a simple database test. - * + * * @author Dave Syer + * @author Christian Dupuis */ public class SimpleDataSourceHealthIndicator implements HealthIndicator> { @@ -72,9 +72,10 @@ public class SimpleDataSourceHealthIndicator implements } @Override - public Map health() { - LinkedHashMap health = new LinkedHashMap(); - health.put("status", "ok"); + public Health health() { + Health health = new Health(); + health.up(); + String product = "unknown"; if (this.dataSource != null) { try { @@ -85,21 +86,19 @@ public class SimpleDataSourceHealthIndicator implements return connection.getMetaData().getDatabaseProductName(); } }); - health.put("database", product); + health.withDetail("database", product); } catch (DataAccessException ex) { - health.put("status", "error"); - health.put("error", ex.getClass().getName() + ": " + ex.getMessage()); + health.down().withException(ex); } String query = detectQuery(product); if (StringUtils.hasText(query)) { try { - health.put("hello", + health.withDetail("hello", this.jdbcTemplate.queryForObject(query, Object.class)); } catch (Exception ex) { - health.put("status", "error"); - health.put("error", ex.getClass().getName() + ": " + ex.getMessage()); + health.down().withException(ex); } } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java new file mode 100644 index 0000000000..5a9e790bc9 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java @@ -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. + * + *

+ * Status provides convenient constants for commonly used states like {@link #UP}, + * {@link #DOWN} or {@link #OUT_OF_SERVICE}. + * + *

+ * 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(); + } +} \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java index fa07500c82..d2370a18c7 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java @@ -16,16 +16,18 @@ package org.springframework.boot.actuate.health; + /** * Default implementation of {@link HealthIndicator} that simply returns {@literal "ok"}. - * + * * @author Dave Syer + * @author Christian Dupuis */ -public class VanillaHealthIndicator implements HealthIndicator { +public class VanillaHealthIndicator implements HealthIndicator { @Override - public String health() { - return "ok"; + public Health health() { + return new Health(Status.UP); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index b77fa0d4be..a17d0a7d2f 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure; -import java.util.Map; - import org.junit.After; import org.junit.Before; 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.ShutdownEndpoint; 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.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; @@ -43,10 +42,11 @@ import static org.junit.Assert.assertTrue; /** * Tests for {@link EndpointAutoConfiguration}. - * + * * @author Dave Syer * @author Phillip Webb * @author Greg Turnquist + * @author Christian Dupuis */ public class EndpointAutoConfigurationTests { @@ -80,7 +80,6 @@ public class EndpointAutoConfigurationTests { } @Test - @SuppressWarnings("unchecked") public void healthEndpoint() { this.context = new AnnotationConfigApplicationContext(); this.context.register(EmbeddedDataSourceConfiguration.class, @@ -88,12 +87,9 @@ public class EndpointAutoConfigurationTests { this.context.refresh(); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); assertNotNull(bean); - Map result = bean.invoke(); + Health result = bean.invoke(); assertNotNull(result); - assertTrue("Wrong result: " + result, - ((Map) result.get("db")).containsKey("status")); - assertTrue("Wrong result: " + result, - ((Map) result.get("db")).containsKey("database")); + assertTrue("Wrong result: " + result, result.getDetails().containsKey("database")); } @Test @@ -104,9 +100,8 @@ public class EndpointAutoConfigurationTests { this.context.refresh(); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); assertNotNull(bean); - Map result = bean.invoke(); + Health result = bean.invoke(); assertNotNull(result); - assertTrue("Wrong result: " + result, result.containsKey("status")); } @Test diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java index 39ee99bd3a..f859eb467d 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java @@ -60,7 +60,6 @@ public class HealthIndicatorAutoConfigurationTests { } } - @SuppressWarnings("rawtypes") @Test public void defaultHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -73,7 +72,6 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } - @SuppressWarnings("rawtypes") @Test public void redisHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -87,7 +85,6 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } - @SuppressWarnings("rawtypes") @Test public void notRedisHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -102,7 +99,6 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } - @SuppressWarnings("rawtypes") @Test public void mongoHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -116,7 +112,6 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } - @SuppressWarnings("rawtypes") @Test public void notMongoHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -131,7 +126,6 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } - @SuppressWarnings("rawtypes") @Test public void combinedHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -143,7 +137,6 @@ public class HealthIndicatorAutoConfigurationTests { assertEquals(2, beans.size()); } - @SuppressWarnings("rawtypes") @Test public void dataSourceHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -157,7 +150,6 @@ public class HealthIndicatorAutoConfigurationTests { .next().getClass()); } - @SuppressWarnings("rawtypes") @Test public void notDataSourceHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -172,7 +164,6 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } - @SuppressWarnings("rawtypes") @Test public void rabbitHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); @@ -186,7 +177,6 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } - @SuppressWarnings("rawtypes") @Test public void notRabbitHealthIndicator() { this.context = new AnnotationConfigApplicationContext(); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java index 75169ded60..86678506d7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java @@ -16,11 +16,14 @@ package org.springframework.boot.actuate.endpoint; -import java.util.HashMap; import java.util.Map; 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.OrderedHealthAggregator; +import org.springframework.boot.actuate.health.Status; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -32,6 +35,7 @@ import static org.junit.Assert.assertThat; * Tests for {@link HealthEndpoint}. * * @author Phillip Webb + * @author Christian Dupuis */ public class HealthEndpointTests extends AbstractEndpointTests { @@ -41,9 +45,8 @@ public class HealthEndpointTests extends AbstractEndpointTests { @Test public void invoke() throws Exception { - Map result = new HashMap(); - result.put("status", "fine"); - assertThat(getEndpointBean().invoke(), equalTo(result)); + Status result = new Status("FINE"); + assertThat(getEndpointBean().invoke().getStatus(), equalTo(result)); } @Configuration @@ -51,18 +54,25 @@ public class HealthEndpointTests extends AbstractEndpointTests { public static class Config { @Bean - public HealthEndpoint endpoint(Map> healthIndicators) { - return new HealthEndpoint(healthIndicators); + public HealthEndpoint endpoint(HealthAggregator healthAggregator, + Map healthIndicators) { + return new HealthEndpoint(healthAggregator, healthIndicators); } @Bean - public HealthIndicator statusHealthIndicator() { - return new HealthIndicator() { + public HealthIndicator statusHealthIndicator() { + return new HealthIndicator() { + @Override - public String health() { - return "fine"; + public Health health() { + return new Health().status(new Status("FINE")); } }; } + + @Bean + public HealthAggregator healthAggregator() { + return new OrderedHealthAggregator(); + } } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java index 03da219085..fa210dce59 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java @@ -20,10 +20,14 @@ import java.util.HashMap; import java.util.Map; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mock; 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.hasEntry; import static org.junit.Assert.assertThat; @@ -31,64 +35,99 @@ import static org.mockito.BDDMockito.given; /** * Tests for {@link CompositeHealthIndicator} - * + * * @author Tyler J. Frederick * @author Phillip Webb + * @author Christian Dupuis */ public class CompositeHealthIndicatorTests { + private HealthAggregator healthAggregator; + @Mock - private HealthIndicator one; + private HealthIndicator one; @Mock - private HealthIndicator two; + private HealthIndicator two; @Mock - private HealthIndicator three; + private HealthIndicator three; @Before public void setup() { MockitoAnnotations.initMocks(this); - given(this.one.health()).willReturn("1"); - given(this.two.health()).willReturn("2"); - given(this.three.health()).willReturn("3"); + given(this.one.health()).willReturn(new Health().withDetail("1", "1")); + given(this.two.health()).willReturn(new Health().withDetail("2", "2")); + given(this.three.health()).willReturn(new Health().withDetail("3", "3")); + + this.healthAggregator = new OrderedHealthAggregator(); } @Test public void createWithIndicators() throws Exception { - Map> indicators = new HashMap>(); + Map indicators = new HashMap(); indicators.put("one", this.one); indicators.put("two", this.two); - CompositeHealthIndicator composite = new CompositeHealthIndicator(indicators); - Map result = composite.health(); - assertThat(result.size(), equalTo(2)); - assertThat(result, hasEntry("one", (Object) "1")); - assertThat(result, hasEntry("two", (Object) "2")); + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator, indicators); + Health result = composite.health(); + assertThat(result.getDetails().size(), equalTo(2)); + assertThat(result.getDetails(), + hasEntry("one", (Object) new Health().withDetail("1", "1"))); + assertThat(result.getDetails(), + hasEntry("two", (Object) new Health().withDetail("2", "2"))); } @Test public void createWithIndicatorsAndAdd() throws Exception { - Map> indicators = new HashMap>(); + Map indicators = new HashMap(); indicators.put("one", this.one); indicators.put("two", this.two); - CompositeHealthIndicator composite = new CompositeHealthIndicator(indicators); + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator, indicators); composite.addHealthIndicator("three", this.three); - Map result = composite.health(); - assertThat(result.size(), equalTo(3)); - assertThat(result, hasEntry("one", (Object) "1")); - assertThat(result, hasEntry("two", (Object) "2")); - assertThat(result, hasEntry("three", (Object) "3")); + Health result = composite.health(); + assertThat(result.getDetails().size(), equalTo(3)); + assertThat(result.getDetails(), + hasEntry("one", (Object) new Health().withDetail("1", "1"))); + assertThat(result.getDetails(), + hasEntry("two", (Object) new Health().withDetail("2", "2"))); + assertThat(result.getDetails(), + hasEntry("three", (Object) new Health().withDetail("3", "3"))); } @Test public void createWithoutAndAdd() throws Exception { - CompositeHealthIndicator composite = new CompositeHealthIndicator(); + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); composite.addHealthIndicator("one", this.one); composite.addHealthIndicator("two", this.two); - Map result = composite.health(); - assertThat(result.size(), equalTo(2)); - assertThat(result, hasEntry("one", (Object) "1")); - assertThat(result, hasEntry("two", (Object) "2")); + Health result = composite.health(); + assertThat(result.getDetails().size(), equalTo(2)); + assertThat(result.getDetails(), + 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 indicators = new HashMap(); + 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); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MongoHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MongoHealthIndicatorTests.java index ba69ef7c9d..16b1d15937 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MongoHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/MongoHealthIndicatorTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.health; -import java.util.Map; - import org.junit.After; import org.junit.Test; import org.mockito.Mockito; @@ -38,7 +36,7 @@ import static org.junit.Assert.assertTrue; /** * Tests for {@link MongoHealthIndicator}. - * + * * @author Christian Dupuis */ public class MongoHealthIndicatorTests { @@ -73,9 +71,9 @@ public class MongoHealthIndicatorTests { commandResult); MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); - Map health = healthIndicator.health(); - assertEquals("ok", health.get("status")); - assertEquals("2.6.4", health.get("version")); + Health health = healthIndicator.health(); + assertEquals(Status.UP, health.getStatus()); + assertEquals("2.6.4", health.getDetails().get("version")); Mockito.verify(commandResult).getString("version"); Mockito.verify(mongoTemplate).executeCommand("{ serverStatus: 1 }"); @@ -88,9 +86,10 @@ public class MongoHealthIndicatorTests { new MongoException("Connection failed")); MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); - Map health = healthIndicator.health(); - assertEquals("error", health.get("status")); - assertTrue(((String) health.get("error")).contains("Connection failed")); + Health health = healthIndicator.health(); + assertEquals(Status.DOWN, health.getStatus()); + assertTrue(((String) health.getDetails().get("error")) + .contains("Connection failed")); Mockito.verify(mongoTemplate).executeCommand("{ serverStatus: 1 }"); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java new file mode 100644 index 0000000000..e18d4e14b4 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java @@ -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 healths = new HashMap(); + 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 healths = new HashMap(); + 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 healths = new HashMap(); + 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()); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java index 957a0e0a02..839bd57e6c 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/RedisHealthIndicatorTests.java @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.health; -import java.util.Map; import java.util.Properties; import org.junit.After; @@ -37,7 +36,7 @@ import static org.junit.Assert.assertTrue; /** * Tests for {@link RedisHealthIndicator}. - * + * * @author Christian Dupuis */ public class RedisHealthIndicatorTests { @@ -76,9 +75,9 @@ public class RedisHealthIndicatorTests { RedisHealthIndicator healthIndicator = new RedisHealthIndicator( redisConnectionFactory); - Map health = healthIndicator.health(); - assertEquals("ok", health.get("status")); - assertEquals("2.8.9", health.get("version")); + Health health = healthIndicator.health(); + assertEquals(Status.UP, health.getStatus()); + assertEquals("2.8.9", health.getDetails().get("version")); Mockito.verify(redisConnectionFactory).getConnection(); Mockito.verify(redisConnection).info(); @@ -95,9 +94,10 @@ public class RedisHealthIndicatorTests { RedisHealthIndicator healthIndicator = new RedisHealthIndicator( redisConnectionFactory); - Map health = healthIndicator.health(); - assertEquals("error", health.get("status")); - assertTrue(((String) health.get("error")).contains("Connection failed")); + Health health = healthIndicator.health(); + assertEquals(Status.DOWN, health.getStatus()); + assertTrue(((String) health.getDetails().get("error")) + .contains("Connection failed")); Mockito.verify(redisConnectionFactory).getConnection(); Mockito.verify(redisConnection).info(); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java index 80452543f0..641396534c 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.health; import java.sql.Connection; -import java.util.Map; import javax.sql.DataSource; @@ -37,7 +36,7 @@ import static org.mockito.Mockito.when; /** * Tests for {@link SimpleDataSourceHealthIndicator}. - * + * * @author Dave Syer */ public class SimpleDataSourceHealthIndicatorTests { @@ -55,31 +54,31 @@ public class SimpleDataSourceHealthIndicatorTests { @Test public void database() { this.indicator.setDataSource(this.dataSource); - Map health = this.indicator.health(); - assertNotNull(health.get("database")); - assertNotNull(health.get("hello")); + Health health = this.indicator.health(); + assertNotNull(health.getDetails().get("database")); + assertNotNull(health.getDetails().get("hello")); } @Test public void customQuery() { this.indicator.setDataSource(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"); - Map health = this.indicator.health(); + Health health = this.indicator.health(); System.err.println(health); - assertNotNull(health.get("database")); - assertEquals("ok", health.get("status")); - assertNotNull(health.get("hello")); + assertNotNull(health.getDetails().get("database")); + assertEquals(Status.UP, health.getStatus()); + assertNotNull(health.getDetails().get("hello")); } @Test public void error() { this.indicator.setDataSource(this.dataSource); this.indicator.setQuery("SELECT COUNT(*) from BAR"); - Map health = this.indicator.health(); - assertNotNull(health.get("database")); - assertEquals("error", health.get("status")); + Health health = this.indicator.health(); + assertNotNull(health.getDetails().get("database")); + assertEquals(Status.DOWN, health.getStatus()); } @Test @@ -90,8 +89,8 @@ public class SimpleDataSourceHealthIndicatorTests { this.dataSource.getConnection().getMetaData()); when(dataSource.getConnection()).thenReturn(connection); this.indicator.setDataSource(dataSource); - Map health = this.indicator.health(); - assertNotNull(health.get("database")); + Health health = this.indicator.health(); + assertNotNull(health.getDetails().get("database")); verify(connection, times(2)).close(); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/VanillaHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/VanillaHealthIndicatorTests.java index 13debb5edf..2813b64333 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/VanillaHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/VanillaHealthIndicatorTests.java @@ -18,12 +18,11 @@ package org.springframework.boot.actuate.health; import org.junit.Test; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertEquals; /** * Tests for {@link VanillaHealthIndicator}. - * + * * @author Phillip Webb */ public class VanillaHealthIndicatorTests { @@ -31,7 +30,7 @@ public class VanillaHealthIndicatorTests { @Test public void ok() throws Exception { VanillaHealthIndicator healthIndicator = new VanillaHealthIndicator(); - assertThat(healthIndicator.health(), equalTo("ok")); + assertEquals(Status.UP, healthIndicator.health().getStatus()); } } diff --git a/spring-boot-samples/spring-boot-sample-actuator-ui/src/test/java/sample/actuator/ui/SampleActuatorUiApplicationPortTests.java b/spring-boot-samples/spring-boot-sample-actuator-ui/src/test/java/sample/actuator/ui/SampleActuatorUiApplicationPortTests.java index 5afad5416e..1eba39ebd1 100644 --- a/spring-boot-samples/spring-boot-sample-actuator-ui/src/test/java/sample/actuator/ui/SampleActuatorUiApplicationPortTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator-ui/src/test/java/sample/actuator/ui/SampleActuatorUiApplicationPortTests.java @@ -75,7 +75,7 @@ public class SampleActuatorUiApplicationPortTests { ResponseEntity entity = new TestRestTemplate().getForEntity( "http://localhost:" + this.managementPort + "/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); - assertEquals("{\"status\":\"ok\"}", entity.getBody()); + assertEquals("{\"status\":\"UP\"}", entity.getBody()); } } diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java index 02701b4621..f604b3e1e9 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java @@ -68,6 +68,6 @@ public class EndpointsPropertiesSampleActuatorApplicationTests { "http://localhost:" + this.port + "/admin/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body: " + entity.getBody(), - entity.getBody().contains("\"status\":\"ok\"")); + entity.getBody().contains("\"status\":\"UP\"")); } } diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java index 848b95ffc3..71bb34079d 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java @@ -73,7 +73,7 @@ public class ManagementAddressActuatorApplicationTests { String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body: " + entity.getBody(), - entity.getBody().contains("\"status\":\"ok\"")); + entity.getBody().contains("\"status\":\"UP\"")); } } diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java index e959ddc85e..111f533b1a 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java @@ -82,7 +82,7 @@ public class ManagementPortSampleActuatorApplicationTests { "http://localhost:" + this.managementPort + "/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body: " + entity.getBody(), - entity.getBody().contains("\"status\":\"ok\"")); + entity.getBody().contains("\"status\":\"UP\"")); } @Test diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java index a662172a97..d7cd5a6d6d 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java @@ -131,7 +131,7 @@ public class SampleActuatorApplicationTests { "http://localhost:" + this.port + "/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body: " + entity.getBody(), - entity.getBody().contains("\"status\":\"ok\"")); + entity.getBody().contains("\"status\":\"UP\"")); } @Test