Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/modules/servers/partials/operate/webadmin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,53 @@ Response codes:
services can still be used.
* 503: At least one check have answered with a Unhealthy status

=== 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

=== Check single component

Performs a health check for the given component. The component is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@

import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.apache.commons.lang3.NotImplementedException;
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.core.healthcheck.ResultStatus;
Expand All @@ -42,6 +47,7 @@
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
Expand All @@ -56,8 +62,9 @@ public class HealthCheckRoutes implements PublicRoutes {

public static final String HEALTHCHECK = "/healthcheck";
public static final String CHECKS = "/checks";

private static final String PARAM_COMPONENT_NAME = "componentName";
private static final String QUERY_PARAM_COMPONENT_NAMES = "check";

private final JsonTransformer jsonTransformer;
private final Set<HealthCheck> healthChecks;
Expand All @@ -81,7 +88,9 @@ public void define(Service service) {
}

public Object validateHealthChecks(Request request, Response response) {
List<Result> results = executeHealthChecks().collectList().block();
Set<ComponentName> selectedComponentNames = getComponentNames(request);
Collection<HealthCheck> selectedHealthChecks = selectHealthChecks(selectedComponentNames);
List<Result> results = executeHealthChecks(selectedHealthChecks).collectList().block();
ResultStatus status = retrieveAggregationStatus(results);
response.status(getCorrespondingStatusCode(status));
return new HeathCheckAggregationExecutionResultDto(status, mapResultToDto(results));
Expand All @@ -102,10 +111,40 @@ public Object performHealthCheckForComponent(Request request, Response response)

public Object getHealthChecks(Request request, Response response) {
return healthChecks.stream()
.map(healthCheck -> new HealthCheckDto(healthCheck.componentName()))
.collect(ImmutableList.toImmutableList());
.map(healthCheck -> new HealthCheckDto(healthCheck.componentName()))
.collect(ImmutableList.toImmutableList());
}

private Collection<HealthCheck> selectHealthChecks(Set<ComponentName> selectedComponentNames) {
if (selectedComponentNames.isEmpty()) {
return healthChecks;
} else {
return getHealthChecks(selectedComponentNames);
}
}

private Set<ComponentName> getComponentNames(Request request) {
return Optional.ofNullable(request.queryParamsValues(QUERY_PARAM_COMPONENT_NAMES))
.stream()
.flatMap(Stream::of)
.map(ComponentName::new)
.collect(ImmutableSet.toImmutableSet());
}

private Collection<HealthCheck> getHealthChecks(Set<ComponentName> selectedComponentNames) {
Set<ComponentName> componentNames = healthChecks.stream().map(HealthCheck::componentName).collect(ImmutableSet.toImmutableSet());
List<ComponentName> nonExistedComponentNames = selectedComponentNames.stream()
.filter(selectedComponentName -> !componentNames.contains(selectedComponentName))
.toList();
if (!nonExistedComponentNames.isEmpty()) {
throw throw404(nonExistedComponentNames.stream().map(ComponentName::getName).toList());
}

return healthChecks.stream()
.filter(healthCheck -> selectedComponentNames.contains(healthCheck.componentName()))
.toList();
}

private int getCorrespondingStatusCode(ResultStatus resultStatus) {
switch (resultStatus) {
case HEALTHY:
Expand Down Expand Up @@ -151,6 +190,10 @@ private void logFailedCheck(Result result) {
}

private Flux<Result> executeHealthChecks() {
return executeHealthChecks(healthChecks);
}

private Flux<Result> executeHealthChecks(Collection<HealthCheck> healthChecks) {
return Flux.fromIterable(healthChecks)
.flatMap(HealthCheck::check, DEFAULT_CONCURRENCY)
.doOnNext(this::logFailedCheck);
Expand All @@ -168,10 +211,19 @@ private ImmutableList<HealthCheckExecutionResultDto> mapResultToDto(List<Result>
.map(HealthCheckExecutionResultDto::new)
.collect(ImmutableList.toImmutableList());
}

private HaltException throw404(String componentName) {
return throw404("Component with name %s cannot be found", componentName);
}

private HaltException throw404(Collection<String> componentNames) {
return throw404("Components with name %s cannot be found",
componentNames.stream().collect(Collectors.joining(", ")));
}

private HaltException throw404(String message, String componentNames) {
return ErrorResponder.builder()
.message("Component with name %s cannot be found", componentName)
.message(message, componentNames)
.statusCode(HttpStatus.NOT_FOUND_404)
.type(ErrorResponder.ErrorType.NOT_FOUND)
.haltError();
Expand Down
Loading