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]

Reply via email to