Skip to content

Commit

Permalink
Add UNDER_MAINTENANCE
Browse files Browse the repository at this point in the history
  • Loading branch information
seonWKim committed Jun 9, 2024
1 parent 8726c68 commit 5f2053f
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public static HealthCheckServiceBuilder builder() {
private final AggregatedHttpResponse healthyResponse;
private final AggregatedHttpResponse degradedResponse;
private final AggregatedHttpResponse unhealthyResponse;
private final AggregatedHttpResponse underMaintenanceResponse;
private final AggregatedHttpResponse stoppingResponse;
private final ResponseHeaders ping;
private final ResponseHeaders notModifiedHeaders;
Expand All @@ -155,11 +156,11 @@ public static HealthCheckServiceBuilder builder() {
private Server server;
private boolean serverStopping;

HealthCheckService(Set<HealthChecker> healthCheckers,
AggregatedHttpResponse healthyResponse, AggregatedHttpResponse degradedResponse,
AggregatedHttpResponse unhealthyResponse,
long maxLongPollingTimeoutMillis, double longPollingTimeoutJitterRate,
long pingIntervalMillis, @Nullable HealthCheckUpdateHandler updateHandler,
HealthCheckService(Set<HealthChecker> healthCheckers, AggregatedHttpResponse healthyResponse,
AggregatedHttpResponse degradedResponse, AggregatedHttpResponse unhealthyResponse,
AggregatedHttpResponse underMaintenanceResponse, long maxLongPollingTimeoutMillis,
double longPollingTimeoutJitterRate, long pingIntervalMillis,
@Nullable HealthCheckUpdateHandler updateHandler,
List<HealthCheckUpdateListener> updateListeners, boolean startHealthy,
Set<TransientServiceOption> transientServiceOptions) {
serverHealth = new SettableHealthChecker(HealthStatus.UNHEALTHY);
Expand Down Expand Up @@ -200,6 +201,7 @@ public static HealthCheckServiceBuilder builder() {
this.healthyResponse = setCommonHeaders(healthyResponse);
this.degradedResponse = setCommonHeaders(degradedResponse);
this.unhealthyResponse = setCommonHeaders(unhealthyResponse);
this.underMaintenanceResponse = setCommonHeaders(underMaintenanceResponse);
stoppingResponse = clearCommonHeaders(unhealthyResponse);
notModifiedHeaders = ResponseHeaders.builder()
.add(this.unhealthyResponse.headers())
Expand Down Expand Up @@ -301,7 +303,7 @@ public void serverStarted(Server server) {
@Override
public void serverStopping(Server server) {
serverStopping = true;
serverHealth.setHealthStatus(HealthStatus.UNHEALTHY);
serverHealth.setHealthStatus(HealthStatus.UNDER_MAINTENANCE);
}

@Override
Expand Down Expand Up @@ -333,6 +335,9 @@ public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exc
useLongPolling = healthStatus == HealthStatus.DEGRADED;
} else if ("\"unhealthy\"".equals(expectedState) || "w/\"unhealthy\"".equals(expectedState)) {
useLongPolling = healthStatus == HealthStatus.UNHEALTHY;
} else if ("\"under_maintenance\"".equals(expectedState) ||
"w/\"under_maintenance\"".equals(expectedState)) {
useLongPolling = healthStatus == HealthStatus.UNDER_MAINTENANCE;
} else {
useLongPolling = false;
}
Expand Down Expand Up @@ -528,11 +533,14 @@ private AggregatedHttpResponse getResponse(HealthStatus healthStatus) {
return stoppingResponse;
}

if (healthStatus == HealthStatus.DEGRADED) {
return degradedResponse;
switch (healthStatus) {
case DEGRADED:
return degradedResponse;
case UNDER_MAINTENANCE:
return underMaintenanceResponse;
default:
return unhealthyResponse;
}

return unhealthyResponse;
}

private void onHealthCheckerUpdate(HealthChecker unused) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,18 @@ public final class HealthCheckServiceBuilder implements TransientServiceBuilder
MediaType.JSON_UTF_8,
"{\"healthy\":true}");

private AggregatedHttpResponse degradedResponse = AggregatedHttpResponse.of(HttpStatus.OK,
MediaType.JSON_UTF_8,
"{\"healthy\":true, " +
"\"degraded\":true}");
private AggregatedHttpResponse degradedResponse =
AggregatedHttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8,
"{\"healthy\":true,\"status\":\"DEGRADED\"}");

private AggregatedHttpResponse unhealthyResponse = AggregatedHttpResponse.of(HttpStatus.SERVICE_UNAVAILABLE,
MediaType.JSON_UTF_8,
"{\"healthy\":false}");

private AggregatedHttpResponse underMaintenanceResponse =
AggregatedHttpResponse.of(HttpStatus.SERVICE_UNAVAILABLE, MediaType.JSON_UTF_8,
"{\"healthy\":false,\"status\":\"UNDER_MAINTENANCE\"}");

private long maxLongPollingTimeoutMillis = TimeUnit.SECONDS.toMillis(DEFAULT_LONG_POLLING_TIMEOUT_SECONDS);
private double longPollingTimeoutJitterRate = DEFAULT_LONG_POLLING_TIMEOUT_JITTER_RATE;
private long pingIntervalMillis = TimeUnit.SECONDS.toMillis(DEFAULT_PING_INTERVAL_SECONDS);
Expand Down Expand Up @@ -119,7 +123,7 @@ public HealthCheckServiceBuilder healthyResponse(AggregatedHttpResponse healthyR
* HTTP/1.1 200 OK
* Content-Type: application/json; charset=utf-8
*
* { "healthy": true, "degraded": true }
* { "healthy": true, "status": "DEGRADED" }
* }</pre>
*
* @return {@code this}
Expand Down Expand Up @@ -149,6 +153,25 @@ public HealthCheckServiceBuilder unhealthyResponse(AggregatedHttpResponse unheal
return this;
}

/**
* Sets the {@link AggregatedHttpResponse} to send when the {@link Service} is under maintenance. The
* following response is sent by default:
*
* <pre>{@code
* HTTP/1.1 503 Service Unavailable
* Content-Type: application/json; charset=utf-8
*
* { "healthy": false, "status": "UNDER_MAINTENANCE" }
* }</pre>
*
* @return {@code this}
*/
public HealthCheckServiceBuilder underMaintenanceResponse(AggregatedHttpResponse underMaintenanceResponse) {
requireNonNull(underMaintenanceResponse, "underMaintenanceResponse");
this.underMaintenanceResponse = copyResponse(underMaintenanceResponse);
return this;
}

/**
* Make a copy just in case the content is modified by the caller or is backed by ByteBuf.
*/
Expand Down Expand Up @@ -330,8 +353,8 @@ public HealthCheckServiceBuilder transientServiceOptions(
public HealthCheckService build() {
checkState(startHealthy || updateHandler != null,
"Healthiness must be updatable by server listener or update handler.");
return new HealthCheckService(healthCheckers.build(),
healthyResponse, degradedResponse, unhealthyResponse,
return new HealthCheckService(healthCheckers.build(), healthyResponse, degradedResponse,
unhealthyResponse, underMaintenanceResponse,
maxLongPollingTimeoutMillis, longPollingTimeoutJitterRate,
pingIntervalMillis, updateHandler, updateListenersBuilder.build(),
startHealthy, transientServiceOptionsBuilder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
* The health status of a {@link Server}.
*/
public enum HealthStatus {
HEALTHY(2, true),
DEGRADED(1, true),
UNHEALTHY(0, false);
HEALTHY(3, true),
DEGRADED(2, true),
UNHEALTHY(1, false),
UNDER_MAINTENANCE(0, false);

private final int priority;
private final boolean isAvailable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.slf4j.Logger;

import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -503,29 +505,34 @@ void checkDegradedStatus() {
ResponseHeaders.of(HttpStatus.OK,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON_UTF_8,
"armeria-lphc", "60, 5"),
HttpData.ofUtf8("{\"healthy\":true, \"degraded\":true}")));
HttpData.ofUtf8("{\"healthy\":true,\"status\":\"DEGRADED\"}")));
}

@Test
void notifyDegradedStatus() throws Exception {
@ParameterizedTest
@ValueSource(strings = { "DEGRADED", "UNHEALTHY", "UNDER_MAINTENANCE" })
void notifyHealthStatus(String healthStatus) throws Exception {
final CompletableFuture<AggregatedHttpResponse> f =
sendLongPollingGet("healthy", "/hc");

// Should not wake up until the server becomes degraded.
assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS))
.isInstanceOf(TimeoutException.class);

// Make the server degraded
checker.setHealthStatus(HealthStatus.DEGRADED);
assertThat(f.get()).isEqualTo(AggregatedHttpResponse.of(
ImmutableList.of(ResponseHeaders.builder(HttpStatus.PROCESSING)
.set("armeria-lphc", "60, 5")
.build()),
ResponseHeaders.of(HttpStatus.OK,
HttpHeaderNames.CONTENT_TYPE, MediaType.JSON_UTF_8,
"armeria-lphc", "60, 5"),
HttpData.ofUtf8("{\"healthy\":true, \"degraded\":true}"),
HttpHeaders.of()));
switch (healthStatus) {
case "DEGRADED":
checker.setHealthStatus(HealthStatus.DEGRADED);
assertThat(f.get().contentUtf8())
.isEqualTo("{\"healthy\":true,\"status\":\"DEGRADED\"}");
break;
case "UNHEALTHY":
checker.setHealthStatus(HealthStatus.UNHEALTHY);
assertThat(f.get().contentUtf8())
.isEqualTo( "{\"healthy\":false}");
break;
case "UNDER_MAINTENANCE":
checker.setHealthStatus(HealthStatus.UNDER_MAINTENANCE);
assertThat(f.get().contentUtf8())
.isEqualTo("{\"healthy\":false,\"status\":\"UNDER_MAINTENANCE\"}");
break;
}
}

private static void verifyDebugEnabled(Logger logger) {
Expand Down

0 comments on commit 5f2053f

Please sign in to comment.