Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE-5705] Support for degraded health #5741

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ final class CpuHealthChecker implements HealthChecker {
@VisibleForTesting
final double targetSystemCpuUsage;

@VisibleForTesting
final double degradedTargetProcessCpuLoad;

@VisibleForTesting
final double degradedTargetSystemCpuUsage;

/**
* Instantiates a new Default cpu health checker.
*
Expand All @@ -100,23 +106,50 @@ final class CpuHealthChecker implements HealthChecker {
*/
CpuHealthChecker(double targetSystemCpuUsage, double targetProcessCpuUsage) {
this(targetSystemCpuUsage, targetProcessCpuUsage,
targetSystemCpuUsage, targetProcessCpuUsage,
currentSystemCpuUsageSupplier, currentProcessCpuUsageSupplier);
}

/**
* Instantiates a new Default cpu health checker.
*
* @param targetSystemCpuUsage the target cpu usage
* @param targetProcessCpuLoad the target process cpu load
* @param degradedTargetSystemCpuUsage the degraded target cpu usage
* @param degradedTargetProcessCpuLoad the degraded target process cpu load
*/
CpuHealthChecker(double targetSystemCpuUsage, double targetProcessCpuLoad,
double degradedTargetSystemCpuUsage, double degradedTargetProcessCpuLoad) {
this(targetSystemCpuUsage, targetProcessCpuLoad,
degradedTargetSystemCpuUsage, degradedTargetProcessCpuLoad,
currentSystemCpuUsageSupplier, currentProcessCpuUsageSupplier);
}

private CpuHealthChecker(double targetSystemCpuUsage, double targetProcessCpuLoad,
double degradedTargetSystemCpuUsage, double degradedTargetProcessCpuLoad,
DoubleSupplier systemCpuUsageSupplier, DoubleSupplier processCpuUsageSupplier) {
checkArgument(targetSystemCpuUsage >= 0 && targetSystemCpuUsage <= 1.0,
"cpuUsage: %s (expected: 0 <= cpuUsage <= 1)", targetSystemCpuUsage);
checkArgument(targetProcessCpuLoad >= 0 && targetProcessCpuLoad <= 1.0,
"processCpuLoad: %s (expected: 0 <= processCpuLoad <= 1)", targetProcessCpuLoad);
checkArgument(degradedTargetSystemCpuUsage >= targetSystemCpuUsage &&
degradedTargetSystemCpuUsage <= 1.0,
"degradedCpuUsage: %s (expected: %s <= degradedCpuUsage <= 1)",
degradedTargetSystemCpuUsage, targetSystemCpuUsage);
checkArgument(degradedTargetProcessCpuLoad >= targetProcessCpuLoad &&
degradedTargetProcessCpuLoad <= 1.0,
"degradedProcessCpuLoad: %s (expected: %s <= degradedProcessCpuLoad <= 1)",
degradedTargetProcessCpuLoad, targetProcessCpuLoad);
this.targetSystemCpuUsage = targetSystemCpuUsage;
this.targetProcessCpuLoad = targetProcessCpuLoad;
this.degradedTargetSystemCpuUsage = degradedTargetSystemCpuUsage;
this.degradedTargetProcessCpuLoad = degradedTargetProcessCpuLoad;
this.systemCpuUsageSupplier = systemCpuUsageSupplier;
this.processCpuUsageSupplier = processCpuUsageSupplier;
checkState(operatingSystemBeanClass != null, "Unable to find an 'OperatingSystemMXBean' class");
checkState(operatingSystemBean != null, "Unable to find an 'OperatingSystemMXBean'");
checkState(systemCpuLoad != null, "Unable to find the method 'OperatingSystemMXBean.getCpuLoad'" +
" or 'OperatingSystemMXBean.getSystemCpuLoad'");
" or 'OperatingSystemMXBean.getSystemCpuLoad'");
checkState(processCpuLoad != null,
"Unable to find the method 'OperatingSystemMXBean.getProcessCpuLoad'");
}
Expand Down Expand Up @@ -167,13 +200,26 @@ private static Class<?> getFirstClassFound(final List<String> classNames) {
*/
@Override
public boolean isHealthy() {
return isHealthy(systemCpuUsageSupplier, processCpuUsageSupplier);
return healthStatus().isHealthy();
}

private boolean isHealthy(
DoubleSupplier currentSystemCpuUsageSupplier, DoubleSupplier currentProcessCpuUsageSupplier) {
/**
* Returns the {@link HealthStatus} representing the System CPU Usage and Processes cpu usage.
*/
@Override
public HealthStatus healthStatus() {
final double currentSystemCpuUsage = currentSystemCpuUsageSupplier.getAsDouble();
final double currentProcessCpuUsage = currentProcessCpuUsageSupplier.getAsDouble();
return currentSystemCpuUsage <= targetSystemCpuUsage && currentProcessCpuUsage <= targetProcessCpuLoad;

if (currentSystemCpuUsage <= targetSystemCpuUsage && currentProcessCpuUsage <= targetProcessCpuLoad) {
return HealthStatus.HEALTHY;
}

if (currentSystemCpuUsage <= degradedTargetSystemCpuUsage &&
currentProcessCpuUsage <= degradedTargetProcessCpuLoad) {
return HealthStatus.DEGRADED;
}

return HealthStatus.UNHEALTHY;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.common.util.UnmodifiableFuture;
import com.linecorp.armeria.server.HttpStatusException;
import com.linecorp.armeria.server.Server;
import com.linecorp.armeria.server.ServiceRequestContext;

/**
* Handler which updates the healthiness of the {@link Server}. Supports {@code PUT}, {@code POST} and
* {@code PATCH} requests and tells if the {@link Server} needs to be marked as healthy or unhealthy.
*/
enum DefaultHealthCheckUpdateHandler implements HealthCheckUpdateHandler {

INSTANCE;
Expand Down Expand Up @@ -62,16 +67,30 @@ private static HealthCheckUpdateResult handlePut(AggregatedHttpRequest req) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}

final JsonNode healthy = json.get("healthy");
if (healthy == null) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}
if (healthy.getNodeType() != JsonNodeType.BOOLEAN) {
if (json.has("healthy")) {
final JsonNode jsonNode = json.get("healthy");
if (jsonNode == null) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}
if (jsonNode.getNodeType() != JsonNodeType.BOOLEAN) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}

return jsonNode.booleanValue() ? HealthCheckUpdateResult.HEALTHY
: HealthCheckUpdateResult.UNHEALTHY;
} else if (json.has("status")) {
final JsonNode status = json.get("status");
if (status == null) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}
if (status.getNodeType() != JsonNodeType.STRING) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}

return getHealthCheckUpdateResult(status);
} else {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}

return healthy.booleanValue() ? HealthCheckUpdateResult.HEALTHY
: HealthCheckUpdateResult.UNHEALTHY;
}

private static HealthCheckUpdateResult handlePatch(AggregatedHttpRequest req) {
Expand All @@ -86,14 +105,21 @@ private static HealthCheckUpdateResult handlePatch(AggregatedHttpRequest req) {
final JsonNode path = patchCommand.get("path");
final JsonNode value = patchCommand.get("value");
if (op == null || path == null || value == null ||
!"replace".equals(op.textValue()) ||
!"/healthy".equals(path.textValue()) ||
!value.isBoolean()) {
!"replace".equals(op.textValue())) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}

return value.booleanValue() ? HealthCheckUpdateResult.HEALTHY
: HealthCheckUpdateResult.UNHEALTHY;
if ("/healthy".equals(path.textValue()) && value.isBoolean()) {
return value.isBoolean() ? value.booleanValue() ? HealthCheckUpdateResult.HEALTHY
: HealthCheckUpdateResult.UNHEALTHY
: HealthCheckUpdateResult.UNHEALTHY;
}

if ("/status".equals(path.textValue()) && value.isTextual()) {
return getHealthCheckUpdateResult(value);
}

throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}

private static JsonNode toJsonNode(AggregatedHttpRequest req) {
Expand All @@ -111,4 +137,21 @@ private static JsonNode toJsonNode(AggregatedHttpRequest req) {
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}
}

private static HealthCheckUpdateResult getHealthCheckUpdateResult(JsonNode status) {
switch (status.textValue()) {
case "HEALTHY":
return HealthCheckUpdateResult.HEALTHY;
case "DEGRADED":
return HealthCheckUpdateResult.DEGRADED;
case "STOPPING":
return HealthCheckUpdateResult.STOPPING;
case "UNHEALTHY":
return HealthCheckUpdateResult.UNHEALTHY;
case "UNDER_MAINTENANCE":
return HealthCheckUpdateResult.UNDER_MAINTENANCE;
default:
throw HttpStatusException.of(HttpStatus.BAD_REQUEST);
}
}
}
Loading
Loading