JAMES-2575 perform a single health check Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/b89b347c Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/b89b347c Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/b89b347c
Branch: refs/heads/master Commit: b89b347c60b3e7275f05add5ff00a975a6778ccb Parents: d553b26 Author: Michael Schnitzler <[email protected]> Authored: Sat Oct 27 00:50:13 2018 +0200 Committer: Benoit Tellier <[email protected]> Committed: Wed Oct 31 08:48:29 2018 +0700 ---------------------------------------------------------------------- .../dto/HealthCheckExecutionResultDto.java | 54 + .../webadmin/routes/HealthCheckRoutes.java | 80 +- .../webadmin/routes/HealthCheckRoutesTest.java | 54 +- src/site/markdown/server/manage-webadmin.md | 4788 +++++++++--------- 4 files changed, 2581 insertions(+), 2395 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/b89b347c/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/HealthCheckExecutionResultDto.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/HealthCheckExecutionResultDto.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/HealthCheckExecutionResultDto.java new file mode 100644 index 0000000..4fb3a12 --- /dev/null +++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/HealthCheckExecutionResultDto.java @@ -0,0 +1,54 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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.apache.james.webadmin.dto; + +import org.apache.james.core.healthcheck.Result; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.google.common.net.UrlEscapers; + +@JsonPropertyOrder({"componentName", "escapedComponentName", "status", "cause"}) +public class HealthCheckExecutionResultDto { + + private final Result healthCheckResult; + + public HealthCheckExecutionResultDto(Result healthCheckResult) { + this.healthCheckResult = healthCheckResult; + } + + public String getComponentName() { + return healthCheckResult.getComponentName().getName(); + } + + public String getEscapedComponentName() { + return UrlEscapers.urlPathSegmentEscaper().escape( + healthCheckResult.getComponentName().getName()); + } + + public String getStatus() { + return healthCheckResult.getStatus().toString(); + } + + public String getCause() { + return healthCheckResult.getCause() + .orElse(null); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/b89b347c/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java index 8ec0b73..acdfb22 100644 --- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java +++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java @@ -20,6 +20,7 @@ package org.apache.james.webadmin.routes; import java.util.List; +import java.util.Optional; import java.util.Set; import javax.inject.Inject; @@ -29,6 +30,9 @@ import javax.ws.rs.Path; import org.apache.james.core.healthcheck.HealthCheck; import org.apache.james.core.healthcheck.Result; import org.apache.james.webadmin.PublicRoutes; +import org.apache.james.webadmin.dto.HealthCheckExecutionResultDto; +import org.apache.james.webadmin.utils.ErrorResponder; +import org.apache.james.webadmin.utils.JsonTransformer; import org.eclipse.jetty.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,9 +40,13 @@ import org.slf4j.LoggerFactory; import com.github.steveash.guavate.Guavate; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import spark.Request; +import spark.Response; import spark.Service; @Api(tags = "Healthchecks") @@ -49,13 +57,13 @@ public class HealthCheckRoutes implements PublicRoutes { public static final String HEALTHCHECK = "/healthcheck"; - + private final JsonTransformer jsonTransformer; private final Set<HealthCheck> healthChecks; - private Service service; @Inject - public HealthCheckRoutes(Set<HealthCheck> healthChecks) { + public HealthCheckRoutes(Set<HealthCheck> healthChecks, JsonTransformer jsonTransformer) { this.healthChecks = healthChecks; + this.jsonTransformer = jsonTransformer; } @Override @@ -65,9 +73,8 @@ public class HealthCheckRoutes implements PublicRoutes { @Override public void define(Service service) { - this.service = service; - - validateHealthchecks(); + service.get(HEALTHCHECK, this::validateHealthchecks, jsonTransformer); + service.get(HEALTHCHECK + "/checks/:componentName", this::performHealthCheckForComponent, jsonTransformer); } @GET @@ -77,15 +84,47 @@ public class HealthCheckRoutes implements PublicRoutes { @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - When one check has failed.") }) - public void validateHealthchecks() { - service.get(HEALTHCHECK, - (request, response) -> { - List<Result> anyUnhealthyOrDegraded = retrieveUnhealthyOrDegradedHealthChecks(); - - anyUnhealthyOrDegraded.forEach(this::logFailedCheck); - response.status(getCorrespondingStatusCode(anyUnhealthyOrDegraded)); - return response; - }); + public Object validateHealthchecks(Request request, Response response) { + List<Result> anyUnhealthyOrDegraded = retrieveUnhealthyOrDegradedHealthChecks(); + + anyUnhealthyOrDegraded.forEach(this::logFailedCheck); + response.status(getCorrespondingStatusCode(anyUnhealthyOrDegraded)); + return response; + } + + @GET + @Path("/checks/{componentName}") + @ApiOperation(value = "Perform the component's health check") + @ApiImplicitParams({ + @ApiImplicitParam( + name="componentName", + required=true, + paramType="path", + dataType="String", + defaultValue="None", + example="/checks/Cassandra%20Backend", + value="The URL encoded name of the component to check.") + }) + public Object performHealthCheckForComponent(Request request, Response response) { + String componentName = request.params("componentName"); + Optional<HealthCheck> optHealthCheck = healthChecks.stream() + .filter(c -> c.componentName().getName().equals(componentName)) + .findFirst(); + + if (optHealthCheck.isPresent()) { + HealthCheck healthCheck = optHealthCheck.get(); + Result result = healthCheck.check(); + logFailedCheck(result); + response.status(getCorrespondingStatusCode(result)); + return new HealthCheckExecutionResultDto(result); + } + else { + throw ErrorResponder.builder() + .message(String.format("Component with name %s cannot be found", componentName)) + .statusCode(HttpStatus.NOT_FOUND_404) + .type(ErrorResponder.ErrorType.NOT_FOUND) + .haltError(); + } } private int getCorrespondingStatusCode(List<Result> anyUnhealthy) { @@ -95,6 +134,17 @@ public class HealthCheckRoutes implements PublicRoutes { return HttpStatus.INTERNAL_SERVER_ERROR_500; } } + + private int getCorrespondingStatusCode(Result result) { + switch(result.getStatus()) { + case HEALTHY: + return HttpStatus.OK_200; + case DEGRADED: + case UNHEALTHY: + default: + return HttpStatus.INTERNAL_SERVER_ERROR_500; + } + } private void logFailedCheck(Result result) { switch (result.getStatus()) { http://git-wip-us.apache.org/repos/asf/james-project/blob/b89b347c/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java index 046005f..fcc4d09 100644 --- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java +++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java @@ -21,6 +21,7 @@ package org.apache.james.webadmin.routes; import static io.restassured.RestAssured.when; import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; +import static org.hamcrest.Matchers.equalTo; import java.util.HashSet; import java.util.Set; @@ -31,6 +32,7 @@ import org.apache.james.core.healthcheck.Result; import org.apache.james.metrics.logger.DefaultMetricFactory; import org.apache.james.webadmin.WebAdminServer; import org.apache.james.webadmin.WebAdminUtils; +import org.apache.james.webadmin.utils.JsonTransformer; import org.eclipse.jetty.http.HttpStatus; import org.junit.After; import org.junit.Before; @@ -42,6 +44,7 @@ public class HealthCheckRoutesTest { private static final ComponentName COMPONENT_NAME_1 = new ComponentName("component-1"); private static final ComponentName COMPONENT_NAME_2 = new ComponentName("component-2"); + private static final ComponentName COMPONENT_NAME_3 = new ComponentName("component 3"); // mind the space private static HealthCheck healthCheck(Result result) { return new HealthCheck() { @@ -65,7 +68,7 @@ public class HealthCheckRoutesTest { healthChecks = new HashSet<>(); webAdminServer = WebAdminUtils.createWebAdminServer( new DefaultMetricFactory(), - new HealthCheckRoutes(healthChecks)); + new HealthCheckRoutes(healthChecks, new JsonTransformer())); webAdminServer.configure(NO_CONFIGURATION); webAdminServer.await(); @@ -131,4 +134,53 @@ public class HealthCheckRoutesTest { .then() .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); } + + @Test + public void performHealthCheckShouldReturnOkWhenHealthCheckIsHealthy() { + healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_1))); + + when() + .get("/checks/{componentName}", COMPONENT_NAME_1.getName()) + .then() + .statusCode(HttpStatus.OK_200); + } + + @Test + public void performHealthCheckShouldReturnNotFoundWhenComponentNameIsUnknown() { + when() + .get("/checks/{componentName}", "unknown") + .then() + .statusCode(HttpStatus.NOT_FOUND_404); + } + + @Test + public void performHealthCheckShouldReturnInternalErrorWhenHealthCheckIsDegraded() { + healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1))); + + when() + .get("/checks/{componentName}", COMPONENT_NAME_1.getName()) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void performHealthCheckShouldReturnInternalErrorWhenHealthCheckIsUnhealthy() { + healthChecks.add(healthCheck(Result.unhealthy(COMPONENT_NAME_1))); + + when() + .get("/checks/{componentName}", COMPONENT_NAME_1.getName()) + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void performHealthCheckShouldReturnProperlyEscapedComponentName() { + healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_3))); + + when() + .get("/checks/{componentName}", COMPONENT_NAME_3.getName()) + .then() + .body("componentName", equalTo("component 3"), + "escapedComponentName", equalTo("component%203")); + } } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
