Add Status endpoint

This commit adds a new `/application/status` endpoint that provides only
the Health's status of an application.

Previously, `/application/health` was returning full health details or
only the status depending on configuration. Those two use cases are now
separate in two endpoints that can be configured, secured and enabled
separately.

Closes gh-9721
pull/10012/merge
Stephane Nicoll 7 years ago
parent b9bbe9ee01
commit 19e211a1c2

@ -39,10 +39,12 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.StatusEndpoint;
import org.springframework.boot.actuate.endpoint.ThreadDumpEndpoint;
import org.springframework.boot.actuate.endpoint.TraceEndpoint;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorFactory;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
@ -90,17 +92,6 @@ public class EndpointAutoConfiguration {
return new EnvironmentEndpoint(environment);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public HealthEndpoint healthEndpoint(
ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<Map<String, HealthIndicator>> healthIndicators) {
return new HealthEndpoint(
healthAggregator.getIfAvailable(() -> new OrderedHealthAggregator()),
healthIndicators.getIfAvailable(Collections::emptyMap));
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ -182,6 +173,34 @@ public class EndpointAutoConfiguration {
return new AuditEventsEndpoint(auditEventRepository);
}
@Configuration
static class HealthEndpointConfiguration {
private final HealthIndicator healthIndicator;
HealthEndpointConfiguration(ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<Map<String, HealthIndicator>> healthIndicators) {
this.healthIndicator = new HealthIndicatorFactory().createHealthIndicator(
healthAggregator.getIfAvailable(OrderedHealthAggregator::new),
healthIndicators.getIfAvailable(Collections::emptyMap));
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(this.healthIndicator);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public StatusEndpoint statusEndpoint() {
return new StatusEndpoint(this.healthIndicator);
}
}
@Configuration
@ConditionalOnBean(Flyway.class)
@ConditionalOnClass(Flyway.class)

@ -20,10 +20,13 @@ import org.springframework.boot.actuate.autoconfigure.ManagementContextConfigura
import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.StatusEndpoint;
import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HealthStatusHttpMapper;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.StatusWebEndpointExtension;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -61,12 +64,27 @@ public class WebEndpointManagementContextConfiguration {
@ConditionalOnBean(value = HealthEndpoint.class, search = SearchStrategy.CURRENT)
public HealthWebEndpointExtension healthWebEndpointExtension(HealthEndpoint delegate,
HealthWebEndpointExtensionProperties extensionProperties) {
HealthWebEndpointExtension webExtension = new HealthWebEndpointExtension(
delegate);
return new HealthWebEndpointExtension(delegate,
createHealthStatusHttpMapper(extensionProperties));
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(value = StatusEndpoint.class, search = SearchStrategy.CURRENT)
public StatusWebEndpointExtension statusWebEndpointExtension(StatusEndpoint delegate,
HealthWebEndpointExtensionProperties extensionProperties) {
return new StatusWebEndpointExtension(delegate,
createHealthStatusHttpMapper(extensionProperties));
}
private HealthStatusHttpMapper createHealthStatusHttpMapper(
HealthWebEndpointExtensionProperties extensionProperties) {
HealthStatusHttpMapper statusHttpMapper = new HealthStatusHttpMapper();
if (extensionProperties.getMapping() != null) {
webExtension.addStatusMapping(extensionProperties.getMapping());
statusHttpMapper.addStatusMapping(extensionProperties.getMapping());
}
return webExtension;
return statusHttpMapper;
}
@Bean

@ -16,15 +16,10 @@
package org.springframework.boot.actuate.endpoint;
import java.util.Map;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose application health.
@ -40,18 +35,9 @@ public class HealthEndpoint {
/**
* Create a new {@link HealthEndpoint} instance.
* @param healthAggregator the health aggregator
* @param healthIndicators the health indicators
* @param healthIndicator the health indicator
*/
public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
healthAggregator);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(getKey(entry.getKey()), entry.getValue());
}
public HealthEndpoint(HealthIndicator healthIndicator) {
this.healthIndicator = healthIndicator;
}
@ -60,17 +46,4 @@ public class HealthEndpoint {
return this.healthIndicator.health();
}
/**
* Turns the bean name into a key that can be used in the map of health information.
* @param name the bean name
* @return the key
*/
private String getKey(String name) {
int index = name.toLowerCase().indexOf("healthindicator");
if (index > 0) {
return name.substring(0, index);
}
return name;
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.ReadOperation;
/**
* {@link Endpoint} to expose application {@link Status}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@Endpoint(id = "status")
public class StatusEndpoint {
private final HealthIndicator healthIndicator;
/**
* Create a new {@link StatusEndpoint} instance.
* @param healthIndicator the health indicator
*/
public StatusEndpoint(HealthIndicator healthIndicator) {
this.healthIndicator = healthIndicator;
}
@ReadOperation
public Health health() {
return Health.status(this.healthIndicator.health().getStatus()).build();
}
}

@ -0,0 +1,127 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.web;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.health.Status;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
/**
* Map a {@link Status} to an HTTP status code.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class HealthStatusHttpMapper {
private Map<String, Integer> statusMapping = new HashMap<>();
/**
* Create a new instance.
*/
public HealthStatusHttpMapper() {
setupDefaultStatusMapping();
}
private void setupDefaultStatusMapping() {
addStatusMapping(Status.DOWN, 503);
addStatusMapping(Status.OUT_OF_SERVICE, 503);
}
/**
* Set specific status mappings.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void setStatusMapping(Map<String, Integer> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping = new HashMap<>(statusMapping);
}
/**
* Add specific status mappings to the existing set.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void addStatusMapping(Map<String, Integer> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping.putAll(statusMapping);
}
/**
* Add a status mapping to the existing set.
* @param status the status to map
* @param httpStatus the http status
*/
public void addStatusMapping(Status status, Integer httpStatus) {
Assert.notNull(status, "Status must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
addStatusMapping(status.getCode(), httpStatus);
}
/**
* Add a status mapping to the existing set.
* @param statusCode the status code to map
* @param httpStatus the http status
*/
public void addStatusMapping(String statusCode, Integer httpStatus) {
Assert.notNull(statusCode, "StatusCode must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
this.statusMapping.put(statusCode, httpStatus);
}
/**
* Return an immutable view of the status mapping.
* @return the http status codes mapped by status name
*/
public Map<String, Integer> getStatusMapping() {
return Collections.unmodifiableMap(this.statusMapping);
}
/**
* Map the specified {@link Status} to an HTTP status code.
* @param status the health {@link Status}
* @return the corresponding HTTP status code
*/
public int mapStatus(Status status) {
String code = getUniformValue(status.getCode());
if (code != null) {
return this.statusMapping.keySet().stream()
.filter((key) -> code.equals(getUniformValue(key)))
.map(this.statusMapping::get).findFirst().orElse(200);
}
return 200;
}
private String getUniformValue(String code) {
if (code == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (char ch : code.toCharArray()) {
if (Character.isAlphabetic(ch) || Character.isDigit(ch)) {
builder.append(Character.toLowerCase(ch));
}
}
return builder.toString();
}
}

@ -16,17 +16,11 @@
package org.springframework.boot.actuate.endpoint.web;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.web.WebEndpointExtension;
import org.springframework.boot.endpoint.web.WebEndpointResponse;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
/**
* {@link WebEndpointExtension} for the {@link HealthEndpoint}.
@ -42,88 +36,21 @@ import org.springframework.util.Assert;
@WebEndpointExtension(endpoint = HealthEndpoint.class)
public class HealthWebEndpointExtension {
private Map<String, Integer> statusMapping = new HashMap<>();
private final HealthEndpoint delegate;
public HealthWebEndpointExtension(HealthEndpoint delegate) {
this.delegate = delegate;
setupDefaultStatusMapping();
}
private final HealthStatusHttpMapper statusHttpMapper;
private void setupDefaultStatusMapping() {
addStatusMapping(Status.DOWN, 503);
addStatusMapping(Status.OUT_OF_SERVICE, 503);
}
/**
* Set specific status mappings.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void setStatusMapping(Map<String, Integer> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping = new HashMap<>(statusMapping);
}
/**
* Add specific status mappings to the existing set.
* @param statusMapping a map of status code to {@link HttpStatus}
*/
public void addStatusMapping(Map<String, Integer> statusMapping) {
Assert.notNull(statusMapping, "StatusMapping must not be null");
this.statusMapping.putAll(statusMapping);
}
/**
* Add a status mapping to the existing set.
* @param status the status to map
* @param httpStatus the http status
*/
public void addStatusMapping(Status status, Integer httpStatus) {
Assert.notNull(status, "Status must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
addStatusMapping(status.getCode(), httpStatus);
}
/**
* Add a status mapping to the existing set.
* @param statusCode the status code to map
* @param httpStatus the http status
*/
public void addStatusMapping(String statusCode, Integer httpStatus) {
Assert.notNull(statusCode, "StatusCode must not be null");
Assert.notNull(httpStatus, "HttpStatus must not be null");
this.statusMapping.put(statusCode, httpStatus);
public HealthWebEndpointExtension(HealthEndpoint delegate,
HealthStatusHttpMapper statusHttpMapper) {
this.delegate = delegate;
this.statusHttpMapper = statusHttpMapper;
}
@ReadOperation
public WebEndpointResponse<Health> getHealth() {
Health health = this.delegate.health();
Integer status = getStatus(health);
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(health, status);
}
private int getStatus(Health health) {
String code = getUniformValue(health.getStatus().getCode());
if (code != null) {
return this.statusMapping.keySet().stream()
.filter((key) -> code.equals(getUniformValue(key)))
.map(this.statusMapping::get).findFirst().orElse(200);
}
return 200;
}
private String getUniformValue(String code) {
if (code == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (char ch : code.toCharArray()) {
if (Character.isAlphabetic(ch) || Character.isDigit(ch)) {
builder.append(Character.toLowerCase(ch));
}
}
return builder.toString();
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.web;
import org.springframework.boot.actuate.endpoint.StatusEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.web.WebEndpointExtension;
import org.springframework.boot.endpoint.web.WebEndpointResponse;
/**
* {@link WebEndpointExtension} for the {@link StatusEndpoint}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@WebEndpointExtension(endpoint = StatusEndpoint.class)
public class StatusWebEndpointExtension {
private final StatusEndpoint delegate;
private final HealthStatusHttpMapper statusHttpMapper;
public StatusWebEndpointExtension(StatusEndpoint delegate,
HealthStatusHttpMapper statusHttpMapper) {
this.delegate = delegate;
this.statusHttpMapper = statusHttpMapper;
}
@ReadOperation
public WebEndpointResponse<Health> getHealth() {
Health health = this.delegate.health();
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(health, status);
}
}

@ -0,0 +1,65 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.health;
import java.util.Map;
import org.springframework.util.Assert;
/**
* Factory to create a {@link HealthIndicator}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class HealthIndicatorFactory {
/**
* Create a {@link CompositeHealthIndicator} based on the specified health indicators.
* @param healthAggregator the {@link HealthAggregator}
* @param healthIndicators the {@link HealthIndicator} instances mapped by name
* @return an {@link HealthIndicator} that delegates to the specified
* {@code healthIndicators}.
*/
public HealthIndicator createHealthIndicator(
HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
healthAggregator);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(getKey(entry.getKey()), entry.getValue());
}
return healthIndicator;
}
/**
* Turns the health indicator name into a key that can be used in the map of health
* information.
* @param name the health indicator name
* @return the key
*/
private String getKey(String name) {
int index = name.toLowerCase().indexOf("healthindicator");
if (index > 0) {
return name.substring(0, index);
}
return name;
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
@ -24,11 +23,14 @@ import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.StatusEndpoint;
import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HealthStatusHttpMapper;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.endpoint.web.StatusWebEndpointExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -42,6 +44,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link WebEndpointManagementContextConfiguration}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
public class WebEndpointManagementContextConfigurationTests {
@ -71,8 +74,8 @@ public class WebEndpointManagementContextConfigurationTests {
HealthWebEndpointExtension extension = context
.getBean(HealthWebEndpointExtension.class);
@SuppressWarnings("unchecked")
Map<String, Integer> statusMappings = (Map<String, Integer>) ReflectionTestUtils
.getField(extension, "statusMapping");
Map<String, Integer> statusMappings = ((HealthStatusHttpMapper) ReflectionTestUtils
.getField(extension, "statusHttpMapper")).getStatusMapping();
assertThat(statusMappings).containsEntry("DOWN", 503);
assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503);
assertThat(statusMappings).containsEntry("CUSTOM", 500);
@ -85,6 +88,35 @@ public class WebEndpointManagementContextConfigurationTests {
"health", HealthEndpointConfiguration.class);
}
@Test
public void statusWebEndpointExtensionIsAutoConfigured() {
beanIsAutoConfigured(StatusWebEndpointExtension.class,
StatusEndpointConfiguration.class);
}
@Test
public void statusMappingCanBeCustomized() {
ApplicationContextRunner contextRunner = contextRunner()
.withPropertyValues("endpoints.health.mapping.CUSTOM=500")
.withUserConfiguration(StatusEndpointConfiguration.class);
contextRunner.run((context) -> {
StatusWebEndpointExtension extension = context
.getBean(StatusWebEndpointExtension.class);
@SuppressWarnings("unchecked")
Map<String, Integer> statusMappings = ((HealthStatusHttpMapper) ReflectionTestUtils
.getField(extension, "statusHttpMapper")).getStatusMapping();
assertThat(statusMappings).containsEntry("DOWN", 503);
assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503);
assertThat(statusMappings).containsEntry("CUSTOM", 500);
});
}
@Test
public void statusWebEndpointExtensionCanBeDisabled() {
beanIsNotAutoConfiguredWhenEndpointIsDisabled(StatusWebEndpointExtension.class,
"status", StatusEndpointConfiguration.class);
}
@Test
public void auditEventsWebEndpointExtensionIsAutoConfigured() {
beanIsAutoConfigured(AuditEventsWebEndpointExtension.class,
@ -149,8 +181,17 @@ public class WebEndpointManagementContextConfigurationTests {
@Bean
public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(new OrderedHealthAggregator(),
Collections.emptyMap());
return new HealthEndpoint(() -> Health.up().build());
}
}
@Configuration
static class StatusEndpointConfiguration {
@Bean
public StatusEndpoint statusEndpoint() {
return new StatusEndpoint(() -> Health.up().build());
}
}

@ -23,10 +23,12 @@ import org.junit.Test;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorFactory;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link HealthEndpoint}.
@ -38,34 +40,27 @@ import static org.assertj.core.api.Assertions.assertThat;
public class HealthEndpointTests {
@Test
public void upAndUpIsAggregatedToUp() throws Exception {
public void statusAndFullDetailsAreExposed() {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build());
healthIndicators.put("upAgain",
() -> new Health.Builder().status(Status.UP).build());
HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(),
healthIndicators);
assertThat(endpoint.health().getStatus()).isEqualTo(Status.UP);
healthIndicators.put("up", () -> new Health.Builder().status(Status.UP)
.withDetail("first", "1").build());
healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP)
.withDetail("second", "2").build());
HealthEndpoint endpoint = new HealthEndpoint(createHealthIndicator(
healthIndicators));
Health health = endpoint.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain");
Health upHealth = (Health) health.getDetails().get("up");
assertThat(upHealth.getDetails()).containsOnly(entry("first", "1"));
Health upAgainHealth = (Health) health.getDetails().get("upAgain");
assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2"));
}
@Test
public void upAndDownIsAggregatedToDown() throws Exception {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build());
healthIndicators.put("down",
() -> new Health.Builder().status(Status.DOWN).build());
HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(),
healthIndicators);
assertThat(endpoint.health().getStatus()).isEqualTo(Status.DOWN);
}
@Test
public void unknownStatusMapsToUnknown() throws Exception {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("status", () -> new Health.Builder().status("FINE").build());
HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(),
healthIndicators);
assertThat(endpoint.health().getStatus()).isEqualTo(Status.UNKNOWN);
private HealthIndicator createHealthIndicator(
Map<String, HealthIndicator> healthIndicators) {
return new HealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators);
}
}

@ -0,0 +1,59 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.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.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorFactory;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.Status;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link StatusEndpoint}.
*
* @author Stephane Nicoll
*/
public class StatusEndpointTests {
@Test
public void onlyStatusIsExposed() {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("up", () -> new Health.Builder().status(Status.UP)
.withDetail("first", "1").build());
healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP)
.withDetail("second", "2").build());
StatusEndpoint endpoint = new StatusEndpoint(createHealthIndicator(
healthIndicators));
Health health = endpoint.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).isEmpty();
}
private HealthIndicator createHealthIndicator(
Map<String, HealthIndicator> healthIndicators) {
return new HealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators);
}
}

@ -26,9 +26,11 @@ import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfi
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.web.HealthStatusHttpMapper;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorFactory;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
@ -97,13 +99,14 @@ public class NoSpringSecurityHealthMvcEndpointIntegrationTests {
@Bean
public HealthEndpoint healthEndpoint(
Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(new OrderedHealthAggregator(), healthIndicators);
return new HealthEndpoint(new HealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators));
}
@Bean
public HealthWebEndpointExtension healthWebEndpointExtension(
HealthEndpoint delegate) {
return new HealthWebEndpointExtension(delegate);
return new HealthWebEndpointExtension(delegate, new HealthStatusHttpMapper());
}
}

@ -24,6 +24,7 @@ import org.junit.runner.RunWith;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorFactory;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@ -67,13 +68,14 @@ public class HealthEndpointWebIntegrationTests {
@Bean
public HealthEndpoint healthEndpoint(
Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(new OrderedHealthAggregator(), healthIndicators);
return new HealthEndpoint(new HealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators));
}
@Bean
public HealthWebEndpointExtension healthWebEndpointExtension(
HealthEndpoint delegate) {
return new HealthWebEndpointExtension(delegate);
return new HealthWebEndpointExtension(delegate, new HealthStatusHttpMapper());
}
@Bean

@ -0,0 +1,108 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.web;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.actuate.endpoint.StatusEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorFactory;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Integration tests for {@link StatusEndpoint} and {@link StatusWebEndpointExtension}
* exposed by Jersey, Spring MVC, and WebFlux.
*
* @author Stephane Nicoll
*/
@RunWith(WebEndpointsRunner.class)
public class StatusEndpointWebIntegrationTests {
private static WebTestClient client;
private static ConfigurableApplicationContext context;
@Test
public void whenStatusIsUp200ResponseIsReturned() throws Exception {
client.get().uri("/application/status").exchange().expectStatus().isOk()
.expectBody().json("{\"status\":\"UP\"}");
}
@Test
public void whenStatusIsDown503ResponseIsReturned() throws Exception {
context.getBean("alphaHealthIndicator", TestHealthIndicator.class)
.setHealth(Health.down().build());
client.get().uri("/application/status").exchange().expectStatus()
.isEqualTo(HttpStatus.SERVICE_UNAVAILABLE)
.expectBody().json("{\"status\":\"DOWN\"}");
}
@Configuration
public static class TestConfiguration {
@Bean
public StatusEndpoint statusEndpoint(
Map<String, HealthIndicator> healthIndicators) {
return new StatusEndpoint(new HealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators));
}
@Bean
public StatusWebEndpointExtension statusWebEndpointExtension(
StatusEndpoint delegate) {
return new StatusWebEndpointExtension(delegate, new HealthStatusHttpMapper());
}
@Bean
public TestHealthIndicator alphaHealthIndicator() {
return new TestHealthIndicator();
}
@Bean
public TestHealthIndicator bravoHealthIndicator() {
return new TestHealthIndicator();
}
}
private static class TestHealthIndicator implements HealthIndicator {
private Health health = Health.up().build();
@Override
public Health health() {
Health result = this.health;
this.health = Health.up().build();
return result;
}
void setHealth(Health health) {
this.health = health;
}
}
}

@ -0,0 +1,69 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.health;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HealthIndicatorFactory}.
*
* @author Phillip Webb
* @author Christian Dupuis
* @author Andy Wilkinson
*/
public class HealthIndicatorFactoryTests {
@Test
public void upAndUpIsAggregatedToUp() throws Exception {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build());
healthIndicators.put("upAgain",
() -> new Health.Builder().status(Status.UP).build());
HealthIndicator healthIndicator = createHealthIndicator(healthIndicators);
assertThat(healthIndicator.health().getStatus()).isEqualTo(Status.UP);
}
@Test
public void upAndDownIsAggregatedToDown() throws Exception {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("up", () -> new Health.Builder().status(Status.UP).build());
healthIndicators.put("down",
() -> new Health.Builder().status(Status.DOWN).build());
HealthIndicator healthIndicator = createHealthIndicator(healthIndicators);
assertThat(healthIndicator.health().getStatus()).isEqualTo(Status.DOWN);
}
@Test
public void unknownStatusMapsToUnknown() throws Exception {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("status", () -> new Health.Builder().status("FINE").build());
HealthIndicator healthIndicator = createHealthIndicator(healthIndicators);
assertThat(healthIndicator.health().getStatus()).isEqualTo(Status.UNKNOWN);
}
private HealthIndicator createHealthIndicator(
Map<String, HealthIndicator> healthIndicators) {
return new HealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators);
}
}

@ -109,6 +109,9 @@ The following technology agnostic endpoints are available:
|`shutdown`
|Allows the application to be gracefully shutdown (not enabled by default).
|`status`
|Show application status information (i.e. `health` status with no additional details)
|`threaddump`
|Performs a thread dump.

Loading…
Cancel
Save