This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new fdf1d5fbaf JAMES-4070 Implement query param to return 503 code if
degraded (#2404)
fdf1d5fbaf is described below
commit fdf1d5fbaf4c0aaef8ebdf7f3496a8840501edc2
Author: hungphan227 <[email protected]>
AuthorDate: Thu Sep 12 14:12:52 2024 +0700
JAMES-4070 Implement query param to return 503 code if degraded (#2404)
---
.../modules/servers/partials/operate/webadmin.adoc | 24 ++++
.../james/webadmin/routes/HealthCheckRoutes.java | 62 +++++++---
.../webadmin/routes/HealthCheckRoutesTest.java | 126 ++++++++++++++++++++-
src/site/markdown/server/manage-webadmin.md | 65 +++++++++++
4 files changed, 256 insertions(+), 21 deletions(-)
diff --git a/docs/modules/servers/partials/operate/webadmin.adoc
b/docs/modules/servers/partials/operate/webadmin.adoc
index 8682405a30..7015e9af04 100644
--- a/docs/modules/servers/partials/operate/webadmin.adoc
+++ b/docs/modules/servers/partials/operate/webadmin.adoc
@@ -94,6 +94,14 @@ Response codes:
services can still be used.
* 503: At least one check have answered with a Unhealthy status
+Additional query parameters are supported:
+
+- `strict` allows you enable the strict mode. In this mode, if any checks have
the result of Degraded or Unhealthy status, the response code will be 500. If
omitted, degraded checks would be reported with status code 200.
+
+....
+curl -XGET http://ip:port/healthcheck?strict
+....
+
=== Check specific components
Performs health checks for the given components. Components are
@@ -141,6 +149,14 @@ Response codes:
services can still be used.
* 503: At least one check have answered with a Unhealthy status
+Additional query parameters are supported:
+
+- `strict` allows you enable the strict mode. In this mode, if any checks have
the result of Degraded or Unhealthy status, the response code will be 500. If
omitted, degraded checks would be reported with status code 200.
+
+....
+curl -XGET
http://ip:port/healthcheck?strict&check=HealthCheck1&check=HealthCheck%20two
+....
+
=== Check single component
Performs a health check for the given component. The component is
@@ -168,6 +184,14 @@ Response codes:
* 404: A component with the given name was not found.
* 503: The check has answered with an Unhealthy status.
+Additional query parameters are supported:
+
+- `strict` allows you enable the strict mode. In this mode, if any checks have
the result of Degraded or Unhealthy status, the response code will be 500. If
omitted, degraded checks would be reported with status code 200.
+
+....
+curl -XGET http://ip:port/healthcheck/checks/{backend-name}%20backend?strict
+....
+
=== List all health checks
This endpoint lists all the available health checks.
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 28fcfe0cf7..275402bacf 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
@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -57,6 +58,38 @@ import spark.Response;
import spark.Service;
public class HealthCheckRoutes implements PublicRoutes {
+ public enum StatusCodeConvertMode {
+ STRICT(resultStatus -> {
+ switch (resultStatus) {
+ case HEALTHY:
+ return HttpStatus.OK_200;
+ case DEGRADED, UNHEALTHY:
+ return HttpStatus.SERVICE_UNAVAILABLE_503;
+ default:
+ throw new NotImplementedException(resultStatus + " is not
supported");
+ }
+ }),
+ RELAXED(resultStatus -> {
+ switch (resultStatus) {
+ case HEALTHY, DEGRADED:
+ return HttpStatus.OK_200;
+ case UNHEALTHY:
+ return HttpStatus.SERVICE_UNAVAILABLE_503;
+ default:
+ throw new NotImplementedException(resultStatus + " is not
supported");
+ }
+ });
+
+ private Function<ResultStatus, Integer> converter;
+
+ StatusCodeConvertMode(Function<ResultStatus, Integer> converter) {
+ this.converter = converter;
+ }
+
+ public int getCorrespondingStatusCode(ResultStatus result) {
+ return converter.apply(result);
+ }
+ }
private static final Logger LOGGER =
LoggerFactory.getLogger(HealthCheckRoutes.class);
@@ -65,6 +98,7 @@ public class HealthCheckRoutes implements PublicRoutes {
private static final String PARAM_COMPONENT_NAME = "componentName";
private static final String QUERY_PARAM_COMPONENT_NAMES = "check";
+ private static final String QUERY_PARAM_STRICT = "strict";
private final JsonTransformer jsonTransformer;
private final Set<HealthCheck> healthChecks;
@@ -89,15 +123,17 @@ public class HealthCheckRoutes implements PublicRoutes {
public Object validateHealthChecks(Request request, Response response) {
Set<ComponentName> selectedComponentNames = getComponentNames(request);
+ StatusCodeConvertMode convertMode = getStatusCodeConvertMode(request);
Collection<HealthCheck> selectedHealthChecks =
selectHealthChecks(selectedComponentNames);
List<Result> results =
executeHealthChecks(selectedHealthChecks).collectList().block();
ResultStatus status = retrieveAggregationStatus(results);
- response.status(getCorrespondingStatusCode(status));
+ response.status(convertMode.getCorrespondingStatusCode(status));
return new HeathCheckAggregationExecutionResultDto(status,
mapResultToDto(results));
}
public Object performHealthCheckForComponent(Request request, Response
response) {
String componentName = request.params(PARAM_COMPONENT_NAME);
+ StatusCodeConvertMode convertMode = getStatusCodeConvertMode(request);
HealthCheck healthCheck = healthChecks.stream()
.filter(c -> c.componentName().getName().equals(componentName))
.findFirst()
@@ -105,7 +141,7 @@ public class HealthCheckRoutes implements PublicRoutes {
Result result = Mono.from(healthCheck.check()).block();
logFailedCheck(result);
- response.status(getCorrespondingStatusCode(result.getStatus()));
+
response.status(convertMode.getCorrespondingStatusCode(result.getStatus()));
return new HealthCheckExecutionResultDto(result);
}
@@ -131,6 +167,12 @@ public class HealthCheckRoutes implements PublicRoutes {
.collect(ImmutableSet.toImmutableSet());
}
+ private StatusCodeConvertMode getStatusCodeConvertMode(Request request) {
+ return
Optional.ofNullable(request.queryParamsValues(QUERY_PARAM_STRICT))
+ .map(s -> StatusCodeConvertMode.STRICT)
+ .orElse(StatusCodeConvertMode.RELAXED);
+ }
+
private Collection<HealthCheck> getHealthChecks(Set<ComponentName>
selectedComponentNames) {
Set<ComponentName> componentNames =
healthChecks.stream().map(HealthCheck::componentName).collect(ImmutableSet.toImmutableSet());
List<ComponentName> nonExistedComponentNames =
selectedComponentNames.stream()
@@ -145,18 +187,6 @@ public class HealthCheckRoutes implements PublicRoutes {
.toList();
}
- private int getCorrespondingStatusCode(ResultStatus resultStatus) {
- switch (resultStatus) {
- case HEALTHY:
- case DEGRADED:
- return HttpStatus.OK_200;
- case UNHEALTHY:
- return HttpStatus.SERVICE_UNAVAILABLE_503;
- default:
- throw new NotImplementedException(resultStatus + " is not
supported");
- }
- }
-
private void logFailedCheck(Result result) {
switch (result.getStatus()) {
case UNHEALTHY:
@@ -189,10 +219,6 @@ public class HealthCheckRoutes implements PublicRoutes {
}
}
- private Flux<Result> executeHealthChecks() {
- return executeHealthChecks(healthChecks);
- }
-
private Flux<Result> executeHealthChecks(Collection<HealthCheck>
healthChecks) {
return Flux.fromIterable(healthChecks)
.flatMap(HealthCheck::check, DEFAULT_CONCURRENCY)
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 e54850f98d..819118addd 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
@@ -208,7 +208,7 @@ class HealthCheckRoutesTest {
}
@Test
- void
validateHealthChecksShouldReturnInternalErrorWhenOneHealthCheckIsDegraded() {
+ void validateHealthChecksShouldReturnOkWhenOneHealthCheckIsDegraded() {
healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1,
"cause")));
healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_2)));
@@ -241,7 +241,7 @@ class HealthCheckRoutesTest {
}
@Test
- void
validateHealthChecksShouldReturnInternalErrorWhenAllHealthCheckAreDegraded() {
+ void validateHealthChecksShouldReturnOkWhenAllHealthCheckAreDegraded() {
healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1,
"cause")));
healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_2,
"cause")));
@@ -304,6 +304,78 @@ class HealthCheckRoutesTest {
.isEqualTo(healthCheckBody);
}
+ @Test
+ void
validateHealthChecksShouldReturn503WhenOneHealthCheckIsDegradedAndStrictModeIsEnabled()
{
+ healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1,
"cause")));
+ healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_2)));
+
+ String healthCheckBody =
+ "{\"status\": \"degraded\"," +
+ " \"checks\": [" +
+ " {" +
+ " \"componentName\": \"component-1\"," +
+ " \"escapedComponentName\": \"component-1\"," +
+ " \"status\": \"degraded\"," +
+ " \"cause\": \"cause\"" +
+ " }," +
+ " {" +
+ " \"componentName\": \"component-2\"," +
+ " \"escapedComponentName\": \"component-2\"," +
+ " \"status\": \"healthy\"," +
+ " \"cause\": null" +
+ "}]}";
+
+ String retrieveBody =
+ given()
+ .queryParam("strict")
+ .when()
+ .get()
+ .then()
+ .statusCode(HttpStatus.SERVICE_UNAVAILABLE_503)
+ .extract()
+ .body().asString();
+
+ assertThatJson(retrieveBody)
+ .when(Option.IGNORING_ARRAY_ORDER)
+ .isEqualTo(healthCheckBody);
+ }
+
+ @Test
+ void
validateHealthChecksShouldReturn503WhenAllHealthCheckAreDegradedAndStrictModeIsEnabled()
{
+ healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1,
"cause")));
+ healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_2,
"cause")));
+
+ String healthCheckBody =
+ "{\"status\": \"degraded\"," +
+ " \"checks\": [" +
+ " {" +
+ " \"componentName\": \"component-1\"," +
+ " \"escapedComponentName\": \"component-1\"," +
+ " \"status\": \"degraded\"," +
+ " \"cause\": \"cause\"" +
+ " }," +
+ " {" +
+ " \"componentName\": \"component-2\"," +
+ " \"escapedComponentName\": \"component-2\"," +
+ " \"status\": \"degraded\"," +
+ " \"cause\": \"cause\"" +
+ "}]}";
+
+ String retrieveBody =
+ given()
+ .queryParam("strict")
+ .when()
+ .get()
+ .then()
+ .statusCode(HttpStatus.SERVICE_UNAVAILABLE_503)
+ .extract()
+ .body().asString();
+
+ assertThatJson(retrieveBody)
+ .when(Option.IGNORING_ARRAY_ORDER)
+ .isEqualTo(healthCheckBody);
+ }
+
@Test
void validateHealthChecksShouldReturnOkWhenSelectedHealthCheckIsHealthy() {
healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_1)));
@@ -371,6 +443,37 @@ class HealthCheckRoutesTest {
.isEqualTo(healthCheckBody);
}
+ @Test
+ void
validateHealthChecksShouldReturn503WhenSelectedHealthCheckIsDegradedAndStrictModeIsEnabled()
{
+ healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_1)));
+ healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_2,
"cause")));
+
+ String healthCheckBody =
+ "{\"status\": \"degraded\"," +
+ " \"checks\": [" +
+ " {" +
+ " \"componentName\": \"component-2\"," +
+ " \"escapedComponentName\": \"component-2\"," +
+ " \"status\": \"degraded\"," +
+ " \"cause\": \"cause\"" +
+ "}]}";
+
+ String retrieveBody =
+ given()
+ .queryParam("strict")
+ .queryParam("check", "component-2")
+ .when()
+ .get()
+ .then()
+ .statusCode(HttpStatus.SERVICE_UNAVAILABLE_503)
+ .extract()
+ .body().asString();
+
+ assertThatJson(retrieveBody)
+ .when(Option.IGNORING_ARRAY_ORDER)
+ .isEqualTo(healthCheckBody);
+ }
+
@Test
void
validateHealthChecksShouldReturnStatusUnHealthyWhenOneOfSelectedHealthChecksIsUnhealthy()
{
healthChecks.add(healthCheck(Result.healthy(COMPONENT_NAME_1)));
@@ -469,7 +572,7 @@ class HealthCheckRoutesTest {
}
@Test
- void
performHealthCheckShouldReturnInternalErrorWhenHealthCheckIsDegraded() {
+ void performHealthCheckShouldReturnOkWhenHealthCheckIsDegraded() {
healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1, "the
cause")));
given()
@@ -499,6 +602,23 @@ class HealthCheckRoutesTest {
.body("status", equalTo(ResultStatus.UNHEALTHY.getValue()))
.body("cause", is(SAMPLE_CAUSE));
}
+
+ @Test
+ void
performHealthCheckShouldReturn503WhenHealthCheckIsDegradedAndStrictModeIsEnabled()
{
+ healthChecks.add(healthCheck(Result.degraded(COMPONENT_NAME_1, "the
cause")));
+
+ given()
+ .pathParam("componentName", COMPONENT_NAME_1.getName())
+ .queryParam("strict")
+ .when()
+ .get("/checks/{componentName}")
+ .then()
+ .statusCode(HttpStatus.SERVICE_UNAVAILABLE_503)
+ .body("componentName", equalTo(NAME_1))
+ .body("escapedComponentName", equalTo(NAME_1))
+ .body("status", equalTo(ResultStatus.DEGRADED.getValue()))
+ .body("cause", equalTo("the cause"));
+ }
@Test
void performHealthCheckShouldReturnProperlyEscapedComponentName() {
diff --git a/src/site/markdown/server/manage-webadmin.md
b/src/site/markdown/server/manage-webadmin.md
index 95b3fb6345..2fb22542b7 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -122,6 +122,63 @@ Response codes:
- 200: All checks have answered with a Healthy or Degraded status. James
services can still be used.
- 503: At least one check have answered with a Unhealthy status
+Additional query parameters are supported:
+
+- `strict` allows you enable the strict mode. In this mode, if any checks have
the result of Degraded or Unhealthy status, the response code will be 500. If
omitted, degraded checks would be reported with status code 200.
+
+```
+curl -XGET http://ip:port/healthcheck?strict
+```
+
+### Check specific components
+
+Performs health checks for the given components. Components are referenced by
their URL encoded names.
+
+```
+curl -XGET
http://ip:port/healthcheck?check=HealthCheck1&check=HealthCheck%20two
+```
+
+Will return a list of healthChecks execution result, with an aggregated result:
+
+```
+{
+ "status": "healthy",
+ "checks": [
+ {
+ "componentName": "HealthCheck1",
+ "escapedComponentName": "HealthCheck1",
+ "status": "healthy"
+ "cause": null
+ },
+ {
+ "componentName": "HealthCheck two",
+ "escapedComponentName": "HealthCheck%20two",
+ "status": "healthy"
+ "cause": null
+ }
+ ]
+}
+```
+
+*status* field can be:
+
+* *healthy*: Component works normally
+* *degraded*: Component works in degraded mode. Some non-critical services may
not be working, or latencies are high, for example. Cause contains explanations.
+* *unhealthy*: The component is currently not working. Cause contains
explanations.
+
+Response codes:
+
+* 200: All checks have answered with a Healthy or Degraded status. James
services can still be used.
+* 503: At least one check have answered with a Unhealthy status
+
+Additional query parameters are supported:
+
+- `strict` allows you enable the strict mode. In this mode, if any checks have
the result of Degraded or Unhealthy status, the response code will be 500. If
omitted, degraded checks would be reported with status code 200.
+
+```
+curl -XGET
http://ip:port/healthcheck?strict&check=HealthCheck1&check=HealthCheck%20two
+```
+
### Check single component
Performs a health check for the given component. The component is referenced
by its URL encoded name.
@@ -146,6 +203,14 @@ Response codes:
- 200: The check has answered with a Healthy or Degraded status.
- 404: A component with the given name was not found.
- 503: The check has anwered with a Unhealthy status.
+
+Additional query parameters are supported:
+
+- `strict` allows you enable the strict mode. In this mode, if any checks have
the result of Degraded or Unhealthy status, the response code will be 500. If
omitted, degraded checks would be reported with status code 200.
+
+```
+curl -XGET http://ip:port/healthcheck/checks/{backend-name}%20backend?strict
+```
### List all health checks
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]