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-9721pull/10012/merge
parent
b9bbe9ee01
commit
19e211a1c2
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue