Return different http return status codes for different system health states
fixes #880
This commit is contained in:
@@ -33,10 +33,12 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
|
||||
@@ -54,6 +56,7 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
@@ -72,16 +75,18 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||
* different port to {@link ServerProperties} a new child context is created, otherwise it
|
||||
* is assumed that endpoint requests will be mapped and handled via an already registered
|
||||
* {@link DispatcherServlet}.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Christian Dupuis
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||
@ConditionalOnWebApplication
|
||||
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
|
||||
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementServerPropertiesAutoConfiguration.class })
|
||||
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementServerPropertiesAutoConfiguration.class })
|
||||
@EnableConfigurationProperties(HealthMvcEndpointProperties.class)
|
||||
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
@@ -89,6 +94,9 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private HealthMvcEndpointProperties healthMvcEndpointProperties;
|
||||
|
||||
@Autowired
|
||||
private ManagementServerProperties managementServerProperties;
|
||||
|
||||
@@ -101,8 +109,8 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public EndpointHandlerMapping endpointHandlerMapping() {
|
||||
EndpointHandlerMapping mapping = new EndpointHandlerMapping(mvcEndpoints()
|
||||
.getEndpoints());
|
||||
EndpointHandlerMapping mapping = new EndpointHandlerMapping(
|
||||
mvcEndpoints().getEndpoints());
|
||||
boolean disabled = ManagementServerPort.get(this.applicationContext) != ManagementServerPort.SAME;
|
||||
mapping.setDisabled(disabled);
|
||||
if (!disabled) {
|
||||
@@ -125,10 +133,12 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
// instantiation of ManagementServerProperties.
|
||||
@Configuration
|
||||
protected static class ApplicationContextFilterConfiguration {
|
||||
|
||||
@Bean
|
||||
public Filter applicationContextIdFilter(ApplicationContext context) {
|
||||
final String id = context.getId();
|
||||
return new OncePerRequestFilter() {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
@@ -153,6 +163,17 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
return new EnvironmentMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
@ConditionalOnExpression("${endpoints.health.enabled:true}")
|
||||
public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate) {
|
||||
HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate);
|
||||
if (this.healthMvcEndpointProperties.getMapping() != null) {
|
||||
healthMvcEndpoint.setStatusMapping(this.healthMvcEndpointProperties.getMapping());
|
||||
}
|
||||
return healthMvcEndpoint;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(MetricsEndpoint.class)
|
||||
@ConditionalOnExpression("${endpoints.metrics.enabled:true}")
|
||||
@@ -184,27 +205,25 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
|
||||
// Ensure close on the parent also closes the child
|
||||
if (this.applicationContext instanceof ConfigurableApplicationContext) {
|
||||
((ConfigurableApplicationContext) this.applicationContext)
|
||||
.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent event) {
|
||||
if (event.getApplicationContext() == EndpointWebMvcAutoConfiguration.this.applicationContext) {
|
||||
childContext.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
((ConfigurableApplicationContext) this.applicationContext).addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent event) {
|
||||
if (event.getApplicationContext() == EndpointWebMvcAutoConfiguration.this.applicationContext) {
|
||||
childContext.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
childContext.refresh();
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
} catch (RuntimeException ex) {
|
||||
// No support currently for deploying a war with management.port=<different>,
|
||||
// and this is the signature of that happening
|
||||
if (ex instanceof EmbeddedServletContainerException
|
||||
|| ex.getCause() instanceof EmbeddedServletContainerException) {
|
||||
logger.warn("Could not start embedded container (management endpoints are still available through JMX)");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
@@ -219,17 +238,14 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
ServerProperties serverProperties;
|
||||
try {
|
||||
serverProperties = beanFactory.getBean(ServerProperties.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
} catch (NoSuchBeanDefinitionException ex) {
|
||||
serverProperties = new ServerProperties();
|
||||
}
|
||||
|
||||
ManagementServerProperties managementServerProperties;
|
||||
try {
|
||||
managementServerProperties = beanFactory
|
||||
.getBean(ManagementServerProperties.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
managementServerProperties = beanFactory.getBean(ManagementServerProperties.class);
|
||||
} catch (NoSuchBeanDefinitionException ex) {
|
||||
managementServerProperties = new ManagementServerProperties();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Configuration properties for the {@link HealthMvcEndpoint}.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "endpoints.health")
|
||||
public class HealthMvcEndpointProperties {
|
||||
|
||||
private Map<String, HttpStatus> mapping = new HashMap<String, HttpStatus>();
|
||||
|
||||
public Map<String, HttpStatus> getMapping() {
|
||||
return this.mapping;
|
||||
}
|
||||
|
||||
public void setMapping(Map<String, HttpStatus> mapping) {
|
||||
this.mapping = mapping;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import org.springframework.util.Assert;
|
||||
* @author Dave Syer
|
||||
* @author Christian Dupuis
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = false)
|
||||
@ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = true)
|
||||
public class HealthEndpoint extends AbstractEndpoint<Health> {
|
||||
|
||||
private final HealthIndicator healthIndicator;
|
||||
@@ -48,8 +48,7 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
|
||||
|
||||
if (healthIndicators.size() == 1) {
|
||||
this.healthIndicator = healthIndicators.values().iterator().next();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
|
||||
healthAggregator);
|
||||
for (Map.Entry<String, HealthIndicator> h : healthIndicators.entrySet()) {
|
||||
|
||||
@@ -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.endpoint.mvc;
|
||||
|
||||
import java.util.Collections;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* Adapter to expose {@link HealthEndpoint} as an {@link MvcEndpoint}.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class HealthMvcEndpoint extends EndpointMvcAdapter {
|
||||
|
||||
private Map<String, HttpStatus> statusMapping;
|
||||
|
||||
public HealthMvcEndpoint(HealthEndpoint delegate) {
|
||||
super(delegate);
|
||||
setupDefaultStatusMapping();
|
||||
}
|
||||
|
||||
public void setStatusMapping(Map<String, HttpStatus> statusMapping) {
|
||||
Assert.notNull(statusMapping, "StatusMapping must not be null");
|
||||
this.statusMapping = statusMapping;
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
@ResponseBody
|
||||
@Override
|
||||
public Object invoke() {
|
||||
if (!this.getDelegate().isEnabled()) {
|
||||
// Shouldn't happen
|
||||
return new ResponseEntity<Map<String, String>>(Collections.singletonMap(
|
||||
"message", "This endpoint is disabled"), HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
Health health = (Health) getDelegate().invoke();
|
||||
Status status = health.getStatus();
|
||||
if (this.statusMapping.containsKey(status.getCode())) {
|
||||
return new ResponseEntity<Health>(health,
|
||||
this.statusMapping.get(status.getCode()));
|
||||
}
|
||||
return health;
|
||||
}
|
||||
|
||||
private void setupDefaultStatusMapping() {
|
||||
this.statusMapping = new HashMap<String, HttpStatus>();
|
||||
this.statusMapping.put(Status.DOWN.getCode(), HttpStatus.SERVICE_UNAVAILABLE);
|
||||
this.statusMapping.put(Status.OUT_OF_SERVICE.getCode(),
|
||||
HttpStatus.SERVICE_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.endpoint.mvc;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link HealthMvcEndpoint}.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
*/
|
||||
public class HealthMvcEndpointTests {
|
||||
|
||||
private HealthEndpoint endpoint = null;
|
||||
|
||||
private HealthMvcEndpoint mvc = null;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
this.endpoint = mock(HealthEndpoint.class);
|
||||
when(this.endpoint.isEnabled()).thenReturn(true);
|
||||
this.mvc = new HealthMvcEndpoint(this.endpoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void up() {
|
||||
when(this.endpoint.invoke()).thenReturn(new Health().up());
|
||||
Object result = this.mvc.invoke();
|
||||
assertTrue(result instanceof Health);
|
||||
assertTrue(((Health) result).getStatus() == Status.UP);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void down() {
|
||||
when(this.endpoint.invoke()).thenReturn(new Health().down());
|
||||
Object result = this.mvc.invoke();
|
||||
assertTrue(result instanceof ResponseEntity);
|
||||
ResponseEntity<Health> response = (ResponseEntity<Health>) result;
|
||||
assertTrue(response.getBody().getStatus() == Status.DOWN);
|
||||
assertEquals(HttpStatus.SERVICE_UNAVAILABLE, response.getStatusCode());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void customMapping() {
|
||||
when(this.endpoint.invoke()).thenReturn(new Health().status(new Status("OK")));
|
||||
this.mvc.setStatusMapping(Collections.singletonMap("OK",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
Object result = this.mvc.invoke();
|
||||
assertTrue(result instanceof ResponseEntity);
|
||||
ResponseEntity<Health> response = (ResponseEntity<Health>) result;
|
||||
assertTrue(response.getBody().getStatus().equals(new Status("OK")));
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,15 @@ 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.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@@ -111,7 +110,6 @@ public class CompositeHealthIndicatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testSerialization() throws Exception {
|
||||
Map<String, HealthIndicator> indicators = new HashMap<String, HealthIndicator>();
|
||||
indicators.put("db1", this.one);
|
||||
@@ -125,9 +123,9 @@ public class CompositeHealthIndicatorTests {
|
||||
Health result = composite.health();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
String test = mapper.writeValueAsString(result);
|
||||
System.out.println(test);
|
||||
assertEquals(
|
||||
"{\"status\":\"UNKOWN\",\"db\":{\"status\":\"UNKOWN\",\"db1\":{\"status\":\"UNKOWN\",\"1\":\"1\"},\"db2\":{\"status\":\"UNKOWN\",\"2\":\"2\"}}}",
|
||||
mapper.writeValueAsString(result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user