JAMES-2526 Introduce health checks routes in WebAdmin
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/c9cbe2c3 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/c9cbe2c3 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/c9cbe2c3 Branch: refs/heads/master Commit: c9cbe2c3554dc5d1474185ccf04f4150bd1d5c13 Parents: f30ffeb Author: Antoine Duprat <adup...@linagora.com> Authored: Wed Aug 22 15:34:24 2018 +0200 Committer: Antoine Duprat <adup...@linagora.com> Committed: Mon Aug 27 14:18:06 2018 +0200 ---------------------------------------------------------------------- server/protocols/webadmin/webadmin-core/pom.xml | 4 + .../webadmin/routes/HealthCheckRoutes.java | 118 ++++++++++++++++ .../webadmin/routes/HealthCheckRoutesTest.java | 135 +++++++++++++++++++ 3 files changed, 257 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/c9cbe2c3/server/protocols/webadmin/webadmin-core/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/pom.xml b/server/protocols/webadmin/webadmin-core/pom.xml index 6d27db4..3641203 100644 --- a/server/protocols/webadmin/webadmin-core/pom.xml +++ b/server/protocols/webadmin/webadmin-core/pom.xml @@ -35,6 +35,10 @@ <dependencies> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>james-core</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-server-jwt</artifactId> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/c9cbe2c3/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 new file mode 100644 index 0000000..d9c7e67 --- /dev/null +++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java @@ -0,0 +1,118 @@ +/**************************************************************** + * 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.routes; + +import java.util.List; +import java.util.Set; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.apache.james.core.healthcheck.HealthCheck; +import org.apache.james.core.healthcheck.Result; +import org.apache.james.webadmin.Routes; +import org.eclipse.jetty.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.steveash.guavate.Guavate; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import spark.Service; + +@Api(tags = "Healthchecks") +@Path(HealthCheckRoutes.HEALTHCHECK) +public class HealthCheckRoutes implements Routes { + + private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckRoutes.class); + + public static final String HEALTHCHECK = "/healthcheck"; + + + private final Set<HealthCheck> healthChecks; + private Service service; + + @Inject + public HealthCheckRoutes(Set<HealthCheck> healthChecks) { + this.healthChecks = healthChecks; + } + + @Override + public void define(Service service) { + this.service = service; + + validateHealthchecks(); + } + + @GET + @ApiOperation(value = "Validate all health checks") + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.OK_200, message = "OK"), + @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; + }); + } + + private int getCorrespondingStatusCode(List<Result> anyUnhealthy) { + if (anyUnhealthy.isEmpty()) { + return HttpStatus.OK_200; + } else { + return HttpStatus.INTERNAL_SERVER_ERROR_500; + } + } + + private void logFailedCheck(Result result) { + switch (result.getStatus()) { + case UNHEALTHY: + LOGGER.error("HealthCheck failed for {} : {}", + result.getComponentName().getName(), + result.getCause().orElse("")); + break; + case DEGRADED: + LOGGER.warn("HealthCheck is unstable for {} : {}", + result.getComponentName().getName(), + result.getCause().orElse("")); + break; + case HEALTHY: + // Here only to fix a warning, such cases are already filtered + break; + } + } + + private List<Result> retrieveUnhealthyOrDegradedHealthChecks() { + return healthChecks.stream() + .map(HealthCheck::check) + .filter(result -> result.isUnHealthy() || result.isDegraded()) + .collect(Guavate.toImmutableList()); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/c9cbe2c3/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 new file mode 100644 index 0000000..e0d5b83 --- /dev/null +++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java @@ -0,0 +1,135 @@ +/**************************************************************** + * 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.routes; + +import static io.restassured.RestAssured.when; +import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.james.core.healthcheck.ComponentName; +import org.apache.james.core.healthcheck.HealthCheck; +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.eclipse.jetty.http.HttpStatus; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import io.restassured.RestAssured; + +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 HealthCheck healthCheck(Result result) { + return new HealthCheck() { + @Override + public ComponentName componentName() { + return result.getComponentName(); + } + + @Override + public Result check() { + return result; + } + }; + } + + private WebAdminServer webAdminServer; + private Set<HealthCheck> healthChecks; + + @Before + public void setUp() throws Exception { + healthChecks = new HashSet<>(); + + webAdminServer = WebAdminUtils.createWebAdminServer( + new DefaultMetricFactory(), + new HealthCheckRoutes(healthChecks)); + + webAdminServer.configure(NO_CONFIGURATION); + webAdminServer.await(); + + RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer) + .setBasePath(HealthCheckRoutes.HEALTHCHECK) + .build(); + } + + @After + public void tearDown() { + webAdminServer.destroy(); + } + + @Test + public void validateHealthchecksShouldReturnOkWhenNoHealthChecks() { + when() + .get() + .then() + .statusCode(HttpStatus.OK_200); + } + + @Test + public void validateHealthchecksShouldReturnOkWhenHealthChecksAreHealthy() { + healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_1))); + healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_2))); + + when() + .get() + .then() + .statusCode(HttpStatus.OK_200); + } + + @Test + public void validateHealthchecksShouldReturnInternalErrorWhenOneHealthCheckIsUnhealthy() { + healthChecks.add(healthCheck(Result.unhealthy(COMPONENT_NAME_1, "cause"))); + healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_2))); + + when() + .get() + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void validateHealthchecksShouldReturnInternalErrorWhenAllHealthChecksAreUnhealthy() { + healthChecks.add(healthCheck(Result.unhealthy(COMPONENT_NAME_1, "cause"))); + healthChecks.add(healthCheck(Result.unhealthy(COMPONENT_NAME_2))); + + when() + .get() + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + @Test + public void validateHealthchecksShouldReturnInternalErrorWhenOneHealthCheckIsDegraded() { + healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1, "cause"))); + healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_2))); + + when() + .get() + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org