From 56b677aaf9255f1dabec71a68489c26efa1c83dd Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 15:57:56 -0600 Subject: [PATCH 01/15] Remove retired 'outcome' and 'state' support --- .../io/helidon/health/HealthCheckResponseImpl.java | 10 +++++----- .../health/HealthCheckResponseProviderImpl.java | 8 ++++---- .../test/java/io/helidon/health/HealthSupportTest.java | 4 ---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/health/health/src/main/java/io/helidon/health/HealthCheckResponseImpl.java b/health/health/src/main/java/io/helidon/health/HealthCheckResponseImpl.java index 7562a9fe937..7fcaa6e8f7c 100644 --- a/health/health/src/main/java/io/helidon/health/HealthCheckResponseImpl.java +++ b/health/health/src/main/java/io/helidon/health/HealthCheckResponseImpl.java @@ -28,13 +28,13 @@ */ class HealthCheckResponseImpl extends HealthCheckResponse { private final String name; - private final Status state; + private final Status status; private final TreeMap data; - HealthCheckResponseImpl(String name, Status state, Map data) { + HealthCheckResponseImpl(String name, Status status, Map data) { // Since this constructor is internally called, I'm harsh on accepted values Objects.requireNonNull(name); - Objects.requireNonNull(state); + Objects.requireNonNull(status); Objects.requireNonNull(data); // I wrap the "data" map in a TreeMap for two reasons. First, I very much @@ -48,7 +48,7 @@ class HealthCheckResponseImpl extends HealthCheckResponse { // and previously created instances should not be impacted if the source map was updated // subsequent to the previous instances being created! this.name = name; - this.state = state; + this.status = status; this.data = new TreeMap<>(data); } @@ -59,7 +59,7 @@ public String getName() { @Override public Status getStatus() { - return state; + return status; } @Override diff --git a/health/health/src/main/java/io/helidon/health/HealthCheckResponseProviderImpl.java b/health/health/src/main/java/io/helidon/health/HealthCheckResponseProviderImpl.java index 05ce73ff3a9..f0533b270c9 100644 --- a/health/health/src/main/java/io/helidon/health/HealthCheckResponseProviderImpl.java +++ b/health/health/src/main/java/io/helidon/health/HealthCheckResponseProviderImpl.java @@ -33,7 +33,7 @@ public HealthCheckResponseBuilder createResponseBuilder() { return new HealthCheckResponseBuilder() { private final Map data = new HashMap<>(); private String name; - private HealthCheckResponse.Status state = HealthCheckResponse.Status.UP; + private HealthCheckResponse.Status status = HealthCheckResponse.Status.UP; @Override public HealthCheckResponseBuilder name(String name) { @@ -72,13 +72,13 @@ public HealthCheckResponseBuilder withData(String key, boolean value) { @Override public HealthCheckResponseBuilder up() { - this.state = HealthCheckResponse.Status.UP; + this.status = HealthCheckResponse.Status.UP; return this; } @Override public HealthCheckResponseBuilder down() { - this.state = HealthCheckResponse.Status.DOWN; + this.status = HealthCheckResponse.Status.DOWN; return this; } @@ -95,7 +95,7 @@ public HealthCheckResponseBuilder status(boolean up) { @Override public HealthCheckResponse build() { - return new HealthCheckResponseImpl(name, state, data); + return new HealthCheckResponseImpl(name, status, data); } }; } diff --git a/health/health/src/test/java/io/helidon/health/HealthSupportTest.java b/health/health/src/test/java/io/helidon/health/HealthSupportTest.java index 5e89a7c514e..82b52bf3348 100644 --- a/health/health/src/test/java/io/helidon/health/HealthSupportTest.java +++ b/health/health/src/test/java/io/helidon/health/HealthSupportTest.java @@ -142,7 +142,6 @@ void noHealthChecksResultsInSuccess() { // Test the JSON final JsonObject json = response.json(); - assertThat(json.getString("outcome"), equalTo("UP")); assertThat(json.getJsonArray("checks"), notNullValue()); assertThat(json.getJsonArray("checks").size(), equalTo(0)); } @@ -178,7 +177,6 @@ void passingHealthChecksResultInSuccess(List goodChecks) { // Test the JSON final JsonObject json = response.json(); - assertThat(json.getString("outcome"), is("UP")); assertThat(json.getJsonArray("checks"), notNullValue()); assertThat(json.getJsonArray("checks"), hasSize(goodChecks.size())); } @@ -196,7 +194,6 @@ void failingHealthChecksResultInFailure(List badChecks) { // Test the JSON final JsonObject json = response.json(); - assertThat(json.getString("outcome"), is("DOWN")); assertThat(json.getJsonArray("checks"), notNullValue()); assertThat(json.getJsonArray("checks"), hasSize(badChecks.size())); } @@ -214,7 +211,6 @@ void brokenHealthChecksResultInFailure(List brokenChecks) { // Test the JSON final JsonObject json = response.json(); - assertThat(json.getString("outcome"), is("DOWN")); assertThat(json.getJsonArray("checks"), notNullValue()); assertThat(json.getJsonArray("checks"), hasSize(brokenChecks.size())); } From 81549849282046df7960f6713546a8cc0299dd45 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 15:58:37 -0600 Subject: [PATCH 02/15] Health 4.0 support: remove outcome, state; support Startup health checks --- .../java/io/helidon/health/HealthSupport.java | 109 ++++++++---------- 1 file changed, 48 insertions(+), 61 deletions(-) diff --git a/health/health/src/main/java/io/helidon/health/HealthSupport.java b/health/health/src/main/java/io/helidon/health/HealthSupport.java index 17749f30f7a..d7bcd9c66fd 100644 --- a/health/health/src/main/java/io/helidon/health/HealthSupport.java +++ b/health/health/src/main/java/io/helidon/health/HealthSupport.java @@ -17,7 +17,6 @@ package io.helidon.health; import java.time.Duration; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -29,6 +28,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -79,10 +79,10 @@ public final class HealthSupport implements Service { private final List allChecks = new LinkedList<>(); private final List livenessChecks = new LinkedList<>(); private final List readinessChecks = new LinkedList<>(); + private final List startupChecks = new LinkedList<>(); private final boolean includeAll; private final Set includedHealthChecks; private final Set excludedHealthChecks; - private final boolean backwardCompatible; private final CorsEnabledServiceHelper corsEnabledServiceHelper; private final MessageBodyWriter jsonpWriter = JsonpSupport.writer(); private final Timeout timeout; @@ -91,24 +91,13 @@ public final class HealthSupport implements Service { private HealthSupport(Builder builder) { this.enabled = builder.enabled; this.webContext = builder.webContext; - this.backwardCompatible = builder.backwardCompatible; corsEnabledServiceHelper = CorsEnabledServiceHelper.create(SERVICE_NAME, builder.crossOriginConfig); if (enabled) { - builder.allChecks - .stream() - .filter(health -> !builder.excludedClasses.contains(health.getClass())) - .forEach(allChecks::add); - - builder.readinessChecks - .stream() - .filter(health -> !builder.excludedClasses.contains(health.getClass())) - .forEach(readinessChecks::add); - - builder.livenessChecks - .stream() - .filter(health -> !builder.excludedClasses.contains(health.getClass())) - .forEach(livenessChecks::add); + collectNonexcludedChecks(builder, builder.allChecks, allChecks::add); + collectNonexcludedChecks(builder, builder.readinessChecks, readinessChecks::add); + collectNonexcludedChecks(builder, builder.livenessChecks, livenessChecks::add); + collectNonexcludedChecks(builder, builder.startupChecks, startupChecks::add); this.includedHealthChecks = new HashSet<>(builder.includedHealthChecks); this.excludedHealthChecks = new HashSet<>(builder.excludedHealthChecks); @@ -134,7 +123,14 @@ public void update(Routing.Rules rules) { rules.any(webContext, corsEnabledServiceHelper.processor()) .get(webContext, this::callAll) .get(webContext + "/live", this::callLiveness) - .get(webContext + "/ready", this::callReadiness); + .get(webContext + "/ready", this::callReadiness) + .get(webContext + "/started", this::callStartup); + } + + private static void collectNonexcludedChecks(Builder builder, List checks, Consumer adder) { + checks.stream() + .filter(health -> !builder.excludedClasses.contains(health.getClass())) + .forEach(adder); } private void callAll(ServerRequest req, ServerResponse res) { @@ -149,6 +145,10 @@ private void callReadiness(ServerRequest req, ServerResponse res) { invoke(res, readinessChecks); } + private void callStartup(ServerRequest req, ServerResponse res) { + invoke(res, startupChecks); + } + void invoke(ServerResponse res, List healthChecks) { // timeout on the asynchronous execution Single result = timeout.invoke(() -> async.invoke(() -> callHealthChecks(healthChecks))); @@ -175,36 +175,32 @@ HealthResponse callHealthChecks(List healthChecks) { .sorted(Comparator.comparing(HcResponse::name)) .collect(Collectors.toList()); - Status state = responses.stream() - .map(HcResponse::state) + Status status = responses.stream() + .map(HcResponse::status) .filter(Status.DOWN::equals) .findFirst() .orElse(Status.UP); - Http.ResponseStatus status = responses.stream() + Http.ResponseStatus httpStatus = responses.stream() .filter(HcResponse::internalError) .findFirst() .map(it -> Http.Status.INTERNAL_SERVER_ERROR_500) - .orElse((state == Status.UP) ? Http.Status.OK_200 : Http.Status.SERVICE_UNAVAILABLE_503); + .orElse((status == Status.UP) ? Http.Status.OK_200 : Http.Status.SERVICE_UNAVAILABLE_503); - JsonObject json = toJson(state, responses); - return new HealthResponse(status, json); + JsonObject json = toJson(status, responses); + return new HealthResponse(httpStatus, json); } - private JsonObject toJson(Status state, List responses) { + private JsonObject toJson(Status status, List responses) { final JsonObjectBuilder jsonBuilder = JSON.createObjectBuilder(); - if (backwardCompatible) { - jsonBuilder.add("outcome", state.toString()); - } - jsonBuilder.add("status", state.toString()); + jsonBuilder.add("status", status.toString()); final JsonArrayBuilder checkArrayBuilder = JSON.createArrayBuilder(); for (HcResponse r : responses) { JsonObjectBuilder checkBuilder = JSON.createObjectBuilder(); checkBuilder.add("name", r.name()); - checkBuilder.add("state", r.state().toString()); - checkBuilder.add("status", r.state().toString()); + checkBuilder.add("status", r.status().toString()); Optional> data = r.data(); data.ifPresent(m -> checkBuilder.add("data", JSON.createObjectBuilder(m))); @@ -280,13 +276,13 @@ public static final class Builder implements io.helidon.common.Builder allChecks = new LinkedList<>(); private final List livenessChecks = new LinkedList<>(); private final List readinessChecks = new LinkedList<>(); + private final List startupChecks = new LinkedList<>(); private final Set> excludedClasses = new HashSet<>(); private final Set includedHealthChecks = new HashSet<>(); private final Set excludedHealthChecks = new HashSet<>(); private String webContext = DEFAULT_WEB_CONTEXT; private boolean enabled = true; - private boolean backwardCompatible = true; private CrossOriginConfig crossOriginConfig; private long timeoutMillis = DEFAULT_TIMEOUT_MILLIS; @@ -313,24 +309,6 @@ public Builder webContext(String path) { return this; } - /** - * Add a health check (or healthchecks) to the list. - * All health checks would get invoked when this endpoint is called (even when - * the result is excluded). - * - * @param healthChecks health check(s) to add - * @return updated builder instance - * @deprecated use {@link #addReadiness(org.eclipse.microprofile.health.HealthCheck...)} or - * {@link #addLiveness(org.eclipse.microprofile.health.HealthCheck...)} instead. - * This method is needed until the microprofile specification removes support for generic HealthChecks (which are - * already deprecated). - */ - @Deprecated - public Builder add(HealthCheck... healthChecks) { - this.allChecks.addAll(Arrays.asList(healthChecks)); - return this; - } - /** * Add a health check to a white list (in case {@link #includeAll} is set to {@code false}. * @@ -397,7 +375,6 @@ public Builder config(Config config) { config.get("include").asList(String.class).ifPresent(list -> list.forEach(this::addIncluded)); config.get("exclude").asList(String.class).ifPresent(list -> list.forEach(this::addExcluded)); config.get("exclude-classes").asList(Class.class).ifPresent(list -> list.forEach(this::addExcludedClass)); - config.get("backward-compatible").asBoolean().ifPresent(this::backwardCompatible); config.get("timeout-millis").asLong().ifPresent(this::timeoutMillis); config.get(CORS_CONFIG_KEY) .as(CrossOriginConfig::create) @@ -480,25 +457,35 @@ public Builder addReadiness(Collection healthChecks) { } /** - * HealthSupport can be disabled by invoking this method. + * Add start-up health check(s). * - * @param enabled whether to enable the health support (defaults to {@code true}) + * @param healthChecks health checks to add * @return updated builder instance */ - public Builder enabled(boolean enabled) { - this.enabled = enabled; + public Builder addStartup(HealthCheck... healthChecks) { + return addStartup(List.of(healthChecks)); + } + + /** + * Add start-up health check(s). + * + * @param healthChecks health checks to add + * @return updated builder instance + */ + public Builder addStartup(Collection healthChecks) { + this.allChecks.addAll(healthChecks); + this.startupChecks.addAll(healthChecks); return this; } /** - * Backward compatibility flag to produce Health 1.X compatible JSON output - * (including "outcome" property). + * HealthSupport can be disabled by invoking this method. * - * @param enabled whether to enable backward compatible mode (defaults to {@code true}) + * @param enabled whether to enable the health support (defaults to {@code true}) * @return updated builder instance */ - public Builder backwardCompatible(boolean enabled) { - this.backwardCompatible = enabled; + public Builder enabled(boolean enabled) { + this.enabled = enabled; return this; } @@ -532,7 +519,7 @@ String name() { return hcr.getName(); } - Status state() { + Status status() { return hcr.getStatus(); } From 4e674e3ca3b7816d0480bd42f1a13a15cd154969 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 16:00:11 -0600 Subject: [PATCH 03/15] MP support for Health 4.0: remove retired 'outcome'; support start-up health checks --- .../health/HealthCdiExtension.java | 9 ++++- .../health/HealthCheckProvider.java | 11 +++++- .../health/HealthMpServiceIT.java | 39 +++++++++++++++++-- ...on.microprofile.health.HealthCheckProvider | 5 ++- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java index f372f97e601..bdb2750b58e 100644 --- a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java +++ b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.Liveness; import org.eclipse.microprofile.health.Readiness; +import org.eclipse.microprofile.health.Startup; import static jakarta.interceptor.Interceptor.Priority.LIBRARY_BEFORE; @@ -96,10 +97,16 @@ void registerHealth(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(Applic .filter(hc -> !builtInHealthChecks.contains(hc)) .forEach(builder::addReadiness); + cdi.select(HealthCheck.class, Startup.Literal.INSTANCE) + .stream() + .filter(hc -> !builtInHealthChecks.contains(hc)) + .forEach(builder::addStartup); + HelidonServiceLoader.create(ServiceLoader.load(HealthCheckProvider.class)) .forEach(healthCheckProvider -> { healthCheckProvider.livenessChecks().forEach(builder::addLiveness); healthCheckProvider.readinessChecks().forEach(builder::addReadiness); + healthCheckProvider.startupChecks().forEach(builder::addStartup); }); RoutingBuilders.create(helidonConfig) diff --git a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java index 05bb2a1b39b..6d653e30bd5 100644 --- a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java +++ b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,4 +46,13 @@ default List readinessChecks() { default List livenessChecks() { return Collections.emptyList(); } + + /** + * Return the provided start-up {@link org.eclipse.microprofile.health.HealthCheck}s. + * + * @return the {@link org.eclipse.microprofile.health.HealthCheck}s + */ + default List startupChecks() { + return Collections.emptyList(); + } } diff --git a/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java b/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java index 296eb57728e..f17d99b0f64 100644 --- a/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java +++ b/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.Liveness; import org.eclipse.microprofile.health.Readiness; +import org.eclipse.microprofile.health.Startup; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -43,6 +44,7 @@ @HelidonTest @AddBean(HealthMpServiceIT.HealthCheckOne.class) @AddBean(HealthMpServiceIT.HealthCheckTwo.class) +@AddBean(HealthMpServiceIT.HealthCheckThree.class) @AddBean(HealthMpServiceIT.HealthCheckBad.class) public class HealthMpServiceIT { @@ -61,6 +63,7 @@ public void shouldAddHealthCheckBeans() { JsonObject json = getHealthJson(); assertThat(healthCheckExists(json, "One"), is(true)); assertThat(healthCheckExists(json, "Two"), is(true)); + assertThat(healthCheckExists(json, "Three"), is(true)); } /** @@ -87,7 +90,9 @@ public void shouldAddProvidedHealthChecks() { () -> assertThat("Three exists", healthCheckExists(json, "Three"), is(true)), () -> assertThat("Four exists", healthCheckExists(json, "Four"), is(true)), () -> assertThat("Five exists", healthCheckExists(json, "Five"), is(true)), - () -> assertThat("Six exists", healthCheckExists(json, "Six"), is(true)) + () -> assertThat("Six exists", healthCheckExists(json, "Six"), is(true)), + () -> assertThat("Seven exists", healthCheckExists(json, "Seven"), is(true)), + () -> assertThat("Eight exists", healthCheckExists(json, "Eight"), is(true)) ); } @@ -109,7 +114,6 @@ private JsonObject getHealthJson() { JsonObject json = (JsonObject) Json.createReader(new StringReader(health)).read(); assertThat(json, is(notNullValue())); - assertThat(json.getJsonString("outcome"), is(notNullValue())); // backward compatibility default return json; } @@ -141,6 +145,20 @@ public HealthCheckResponse call() { } } + /** + * A Test {@link HealthCheck} bean for start-up that should be discovered + * by CDI and added to the health check endpoint. + */ + @Startup + public static class HealthCheckThree + implements HealthCheck { + + @Override + public HealthCheckResponse call() { + return HealthCheckResponse.builder().name("Three").up().build(); + } + } + /** * A test {@link HealthCheck} bean that should be NOT discovered * as it does not have the {@link org.eclipse.microprofile.health.Liveness} @@ -186,6 +204,21 @@ public List livenessChecks() { } } + /** + * A test {@link HealthCheckProvider} bean that should be discovered + * by the service loader and its provided start-up {@link HealthCheck}s added + * to the health check endpoint. + */ + public static class HealthCheckProviderThree + implements HealthCheckProvider { + @Override + public List startupChecks() { + return Arrays.asList( + new HealthCheckStub("Seven"), + new HealthCheckStub("Eight")); + } + } + /** * A {@link HealthCheck} stub. */ diff --git a/microprofile/health/src/test/resources/META-INF/services/io.helidon.microprofile.health.HealthCheckProvider b/microprofile/health/src/test/resources/META-INF/services/io.helidon.microprofile.health.HealthCheckProvider index 1c0248a3d81..56181f6c68a 100644 --- a/microprofile/health/src/test/resources/META-INF/services/io.helidon.microprofile.health.HealthCheckProvider +++ b/microprofile/health/src/test/resources/META-INF/services/io.helidon.microprofile.health.HealthCheckProvider @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 Oracle and/or its affiliates. +# Copyright (c) 2019, 2021 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,4 +15,5 @@ # io.helidon.microprofile.health.HealthMpServiceIT$HealthCheckProviderOne -io.helidon.microprofile.health.HealthMpServiceIT$HealthCheckProviderTwo \ No newline at end of file +io.helidon.microprofile.health.HealthMpServiceIT$HealthCheckProviderTwo +io.helidon.microprofile.health.HealthMpServiceIT$HealthCheckProviderThree From 594cf82b6cb01b2606f2fb5f31d5d338354b8376 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 16:00:29 -0600 Subject: [PATCH 04/15] Re-enable health TCK --- microprofile/tests/tck/tck-health/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/microprofile/tests/tck/tck-health/pom.xml b/microprofile/tests/tck/tck-health/pom.xml index 8c98c440950..f2362924479 100644 --- a/microprofile/tests/tck/tck-health/pom.xml +++ b/microprofile/tests/tck/tck-health/pom.xml @@ -30,11 +30,6 @@ tck-health Helidon Microprofile Tests TCK Health - - - true - - io.helidon.microprofile.tests From 52cdaaa99e813734521175596521dfe92530e2c1 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 16:00:50 -0600 Subject: [PATCH 05/15] Update docs for health 4.0 --- docs/mp/guides/04_health.adoc | 161 +++++++++++++++++++++++---- docs/mp/health/01_introduction.adoc | 29 +++-- docs/se/guides/04_health.adoc | 81 ++++++++------ docs/se/health/01_health.adoc | 22 +--- docs/se/health/02_health_in_k8s.adoc | 11 +- 5 files changed, 214 insertions(+), 90 deletions(-) diff --git a/docs/mp/guides/04_health.adoc b/docs/mp/guides/04_health.adoc index a411d877113..adf21a07689 100644 --- a/docs/mp/guides/04_health.adoc +++ b/docs/mp/guides/04_health.adoc @@ -77,17 +77,14 @@ curl http://localhost:8080/health .JSON response: ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "deadlock", - "state": "UP", "status": "UP" }, { "name": "diskSpace", - "state": "UP", "status": "UP", "data": { "free": "325.54 GB", @@ -99,7 +96,6 @@ curl http://localhost:8080/health }, { "name": "heapMemory", - "state": "UP", "status": "UP", "data": { "free": "230.87 MB", @@ -115,10 +111,6 @@ curl http://localhost:8080/health } ---- -NOTE: In MicroProfile Health 2.0 `outcome` and `state` were replaced by `status` in the JSON response wire format. -Helidon currently provides both fields for backwards compatibility, but use of `outcome` and `state` is deprecated -and will be removed in a future release. You should rely on `status` instead. - === Custom Liveness Health Checks You can create application-specific custom health checks and integrate them with Helidon @@ -162,12 +154,10 @@ curl http://localhost:8080/health/live .JSON response: ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "LivenessCheck", - "state": "UP", "status": "UP", "data": { "time": 1566338255331 @@ -177,7 +167,7 @@ curl http://localhost:8080/health/live } ---- -=== Custom Readiness Health Check +=== Custom Readiness Health Checks You can add a readiness check to indicate that the application is ready to be used. In this example, the server will wait five seconds before it becomes ready. @@ -207,7 +197,7 @@ public class GreetReadinessCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("ReadinessCheck") // <3> - .state(isReady()) + .status(isReady()) .withData("time", readyTime.get()) .build(); } @@ -246,12 +236,10 @@ curl -v http://localhost:8080/health/ready < HTTP/1.1 503 Service Unavailable // <1> ... { - "outcome": "DOWN", "status": "DOWN", "checks": [ { "name": "ReadinessCheck", - "state": "DOWN", "status": "DOWN", "data": { "time": 1566399775700 @@ -275,12 +263,10 @@ curl -v http://localhost:8080/health/ready < HTTP/1.1 200 OK // <1> ... { - "outcome": "UP", "status": "UP", "checks": [ { "name": "ReadinessCheck", - "state": "UP", "status": "UP", "data": { "time": 1566399775700 @@ -291,10 +277,121 @@ curl -v http://localhost:8080/health/ready ---- <1> The HTTP status is `200` indicating that the application is ready. +=== Custom Startup Health Checks + +You can add a startup check to indicate whether or not the application has initialized to the point that the other health checks make sense. +In this example, the server will wait eight seconds before it declares itself started. + +[source,java] +.Create a new `GreetStartedCheck` class with the following content: +---- + +package io.helidon.examples.quickstart.mp; + +import java.time.Duration; // <1> +import java.util.concurrent.atomic.AtomicLong; +import jakarta.enterprise.context.ApplicationScoped; + +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Started; + +@Started // <2> +@ApplicationScoped +public class GreetStartedCheck implements HealthCheck { + private AtomicLong readyTime = new AtomicLong(0); + + + @Override + public HealthCheckResponse call() { + return HealthCheckResponse.named("StartedCheck") // <3> + .status(isReady()) + .withData("time", readyTime.get()) + .build(); + } + + public void onStartUp( + @Observes @Initialized(ApplicationScoped.class) Object init) { + readyTime = new AtomicLong(System.currentTimeMillis()); // <4> + } + + /** + * Become ready after 5 seconds + * + * @return true if application ready + */ + private boolean isStarted() { + return Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 8; + } +} +---- +<1> Include additional imports. +<2> Annotation indicating that this is a startup health-check. +<3> Build the `HealthCheckResponse` with status `UP` after eight seconds, else `DOWN`. +<4> Initialize the time at startup of Helidon; the application will declare itself as started eight seconds later. + + +[source,bash] +.Build and run the application. Issue the curl command with -v within five seconds and you will see that the application has not yet started: +---- +curl -v http://localhost:8080/health/started +---- + +[source,json] +.HTTP response: +---- +... +< HTTP/1.1 503 Service Unavailable // <1> +... +{ + "status": "DOWN", + "checks": [ + { + "name": "StartedCheck", + "status": "DOWN", + "data": { + "time": 1566399775700 + } + } + ] +} +---- +<1> The HTTP status is `503` since the application has not started. + +[source,bash] +.After eight seconds you will see the application has started: +---- +curl -v http://localhost:8080/health/started +---- + +[source,json] +.JSON response: +---- +... +< HTTP/1.1 200 OK // <1> +... +{ + "status": "UP", + "checks": [ + { + "name": "StartedCheck", + "status": "UP", + "data": { + "time": 1566399775700 + } + } + ] +} +---- +<1> The HTTP status is `200` indicating that the application is started. + When using the health check URLs, you can get the following health check data: -* custom liveness only - http://localhost:8080/health/live -* custom readiness only - http://localhost:8080/health/ready +* liveness only - http://localhost:8080/health/live +* readiness only - http://localhost:8080/health/ready +* startup checks only - http://localhost:8080/health/started * all health check data - http://localhost:8080/health [source,bash] @@ -307,12 +404,10 @@ curl http://localhost:8080/health .JSON response: ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "LivenessCheck", - "state": "UP", "status": "UP", "data": { "time": 1566403431536 @@ -320,7 +415,13 @@ curl http://localhost:8080/health }, { "name": "ReadinessCheck", - "state": "UP", + "status": "UP", + "data": { + "time": 1566403280639 + } + }, + { + "name": "StartedCheck", "status": "UP", "data": { "time": 1566403280639 @@ -384,6 +485,7 @@ health: curl http://localhost:8080/myhealth curl http://localhost:8080/myhealth/live curl http://localhost:8080/myhealth/ready +curl http://localhost:8080/myhealth/started ---- The following example will change the root path and the health port. @@ -414,12 +516,13 @@ health: curl http://localhost:8081/myhealth curl http://localhost:8081/myhealth/live curl http://localhost:8081/myhealth/ready +curl http://localhost:8081/myhealth/started ---- -=== Using Liveness and Readiness Health Checks with Kubernetes +=== Using Liveness, Readiness, and Startup Health Checks with Kubernetes The following example shows how to integrate the Helidon health check API with an application that implements -health endpoints for the Kubernetes liveness and readiness probes. +health endpoints for the Kubernetes liveness, readiness, and startup probes. *Delete the contents of `application.yaml` so that the default health endpoint path and port are used.* @@ -486,6 +589,14 @@ spec: initialDelaySeconds: 5 // <6> periodSeconds: 2 timeoutSeconds: 3 + startupProbe: + httpGet: + path: /health/started // <7> + port: 8080 + initialDelaySeconds: 8 // <8> + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 --- ---- <1> A service of type `NodePort` that serves the default routes on port `8080`. @@ -494,6 +605,8 @@ spec: <4> The liveness probe configuration. <5> The HTTP endpoint for the readiness probe. <6> The readiness probe configuration. +<7> The HTTP endpoint for the startup probe. +<8> The startup probe configuration. [source,bash] @@ -531,7 +644,7 @@ kubectl delete -f ./health.yaml This guide demonstrated how to use health checks in a Helidon MP application as follows: * Access the default health checks -* Create and use custom readiness and liveness checks +* Create and use custom readiness, liveness, and startup checks * Customize the health check root path and port * Integrate Helidon health check API with Kubernetes diff --git a/docs/mp/health/01_introduction.adoc b/docs/mp/health/01_introduction.adoc index 5a2b4d4eb8f..bd74e60561c 100644 --- a/docs/mp/health/01_introduction.adoc +++ b/docs/mp/health/01_introduction.adoc @@ -61,9 +61,9 @@ implement health checks as part of your microservice that are specific to your s == Concepts -=== Liveness and Readiness Checks +=== Liveness, Readiness, and Startup Checks -MicroProfile Health supports two types of health checks: +MicroProfile Health supports three types of health checks: _Liveness_ checks report whether the runtime environment in which the service is running is sufficient to support the work the service performs. @@ -77,17 +77,17 @@ _Readiness_ checks report whether the service is _currently_ capable of performi A service that reports `DOWN` for its readiness cannot _at the moment_ do its job, but at some future point it might become able to do so without requiring a restart. -The following table describes more about these two types of health checks, including how an orchestrator +_Startup_ checks indicate whether the service has started to the point where liveness and readiness checks even make sense. + A service reporting `DOWN` for a startup check is still initializing itself and normally will report `UP`, assuming it has started successfully. + +The following table describes more about these types of health checks, including how an orchestrator such as Kubernetes might react. === Known Health Check Endpoints A MicroProfile-compliant service reports its health via known REST endpoints. Helidon MP provides these endpoints automatically as part of every MP microservice. -External management tools (or `curl` or browsers) retrieve liveness via `/health/live` and -readiness via `/health/ready`. - -The following table summarizes the two types of health checks in MicroProfile Health. +External management tools (or `curl` or browsers) retrieve health checks using the REST endpoints in he following table which summarizes the types of health checks in MicroProfile Health. .Types of Health Checks |=== @@ -103,6 +103,11 @@ The following table summarizes the two types of health checks in MicroProfile He |`/health/ready` |Diverts requests away from the instance; periodically rechecks readiness and resumes traffic once the microservice reports itself as ready. + +|startup +|whether the microservice has initialized to the point where liveness and readiness checks might pass +|`/health/started` +|Treats the instance as still starting up; does not check liveness or readiness until the startup [robe reports success or times out according to its configuration. |=== === Built-in and Custom Health Checks @@ -110,19 +115,19 @@ microservice reports itself as ready. ==== Built-in Health Checks Helidon provides built-in, default checks for each endpoint. The built-in liveness checks include various environmental values, such as whether the JVM has detected deadlocks -or whether there is sufficient heap space. The built-in readiness check always reports `UP`. +or whether there is sufficient heap space. The built-in readiness and startup checks always report `UP`. You can see all the defaults by accessing any Helidon MP microservice's `/health/live` endpoint and viewing the response. ==== Custom Health Checks -Add your own liveness or readiness checks by adding a Java class for each check. -Each custom check must implement the `HealthCheck` interface, and you add either the `@Liveness` or -the `@Readiness` annotation to the class. +Add your own liveness, readiness, or startup checks by adding a Java class for each check. +Each custom check must implement the `HealthCheck` interface, and you add either the `@Liveness`, `@Startup`, or + `@Readiness` annotation to the class. === Next Steps Add custom health checks to your own microservices. The <> shows how to create a -sample project and add custom liveness and readiness health checks. \ No newline at end of file +sample project and add custom health checks. \ No newline at end of file diff --git a/docs/se/guides/04_health.adoc b/docs/se/guides/04_health.adoc index 603b28e32ec..70de062eeec 100644 --- a/docs/se/guides/04_health.adoc +++ b/docs/se/guides/04_health.adoc @@ -106,17 +106,14 @@ curl http://localhost:8080/health .JSON response: ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "deadlock", - "state": "UP", "status": "UP" }, { "name": "diskSpace", - "state": "UP", "status": "UP", "data": { "free": "319.58 GB", @@ -128,7 +125,6 @@ curl http://localhost:8080/health }, { "name": "heapMemory", - "state": "UP", "status": "UP", "data": { "free": "196.84 MB", @@ -144,10 +140,6 @@ curl http://localhost:8080/health } ---- -NOTE: In MicroProfile Health 2.0 `outcome` and `state` were replaced by `status` in the JSON response wire format. -Helidon currently provides both fields for backwards compatibility, but use of `outcome` and `state` is deprecated -and will be removed in a future release. You should rely on `status` instead. - === Custom Liveness Health Checks You can create application specific custom health checks and integrate them with Helidon @@ -188,11 +180,11 @@ curl http://localhost:8080/health .JSON response: ---- { - "outcome": "UP", + "status": "UP", "checks": [ { "name": "LivenessCheck", - "state": "UP", + "status": "UP", "data": { "time": 1546958376613 } @@ -265,12 +257,10 @@ curl -v http://localhost:8080/health/ready < HTTP/1.1 503 Service Unavailable // <1> ... { - "outcome": "DOWN", "status": "DOWN", "checks": [ { "name": "ReadinessCheck", - "state": "DOWN", "status": "DOWN", "data": { "time,": 0 @@ -294,12 +284,10 @@ curl -v http://localhost:8080/health/ready < HTTP/1.1 200 OK // <1> ... { - "outcome": "UP", "status": "UP", "checks": [ { "name": "ReadinessCheck", - "state": "UP", "status": "UP", "data": { "time,": 1566243562097 @@ -310,15 +298,17 @@ curl -v http://localhost:8080/health/ready ---- <1> The HTTP status is `200` indicating that the application is ready. + When using the health-check URLs, you can get the following health-check data * liveness only - http://localhost:8080/health/live * readiness only - http://localhost:8080/health/ready -* both - http://localhost:8080/health +* start-up only - http://localhost:8080/health/started +* all - http://localhost:8080/health [source,bash] -.Get both liveness and readiness data from a single query: +.Get all of liveness, readiness, and start-up data from a single query: ---- curl http://localhost:8080/health ---- @@ -327,12 +317,10 @@ curl http://localhost:8080/health .JSON response: ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "LivenessCheck", - "state": "UP", "status": "UP", "data": { "time": 1566244094548 @@ -340,7 +328,6 @@ curl http://localhost:8080/health }, { "name": "ReadinessCheck", - "state": "UP", "status": "UP", "data": { "time,": 1566244093012 @@ -352,7 +339,7 @@ curl http://localhost:8080/health === Combine Built-In and Custom Health Checks -You can combine built-in and custom health-checks using the same HealthSupport builder. +You can combine built-in and custom health-checks using the same `HealthSupport` builder. [source,java] .Register a custom health-check in the `Main.createRouting` method: @@ -364,8 +351,12 @@ HealthSupport health = HealthSupport.builder() .withData("time", System.currentTimeMillis()) .build()) .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck") - .state (readyTime.get() != 0 ) - .withData( "time", readyTime.get()) + .status(readyTime.get() != 0) + .withData("time", readyTime.get()) + .build()) + .addStartup(() -> HealthCheckResponse.named("StartedCheck") + .status(readyTime.get() != 0) + .withData("time", readyTime.get()) .build()) .build(); ---- @@ -381,12 +372,10 @@ curl http://localhost:8080/health .JSON response: ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "LivenessCheck", - "state": "UP", "status": "UP", "data": { "time": 1566245527673 @@ -394,7 +383,12 @@ curl http://localhost:8080/health }, { "name": "ReadinessCheck", - "state": "UP", + "status": "UP", + "data": { + "time,": 1566245527620 + }, + { + "name": "StartedCheck", "status": "UP", "data": { "time,": 1566245527620 @@ -402,12 +396,10 @@ curl http://localhost:8080/health }, { "name": "deadlock", - "state": "UP", "status": "UP" }, { "name": "diskSpace", - "state": "UP", "status": "UP", "data": { "free": "326.17 GB", @@ -419,7 +411,6 @@ curl http://localhost:8080/health }, { "name": "heapMemory", - "state": "UP", "status": "UP", "data": { "free": "247.76 MB", @@ -466,11 +457,11 @@ curl http://localhost:8080/probe/live .JSON response: ---- { - "outcome": "UP", + "status": "UP", "checks": [ { "name": "livenessProbe", - "state": "UP", + "status": "UP", "data": { "time": 1546958376613 } @@ -479,13 +470,13 @@ curl http://localhost:8080/probe/live } ---- -=== Using Liveness and Readiness Health Checks with Kubernetes +=== Using Liveness, Readiness, and Startup Health Checks with Kubernetes The following example shows how to integrate the Helidon health API in an application that implements -health endpoints for the Kubernetes liveness and readiness probes. +health endpoints for the Kubernetes liveness, readiness, and startup probes. [source,java] -.Change the `HealthSupport` builder in the `Main.createRouting` method to use the built-in liveness checks, a custom liveness check, and a readiness check: +.Change the `HealthSupport` builder in the `Main.createRouting` method to use the built-in liveness checks and custom liveness, readiness, and started checks: ---- HealthSupport health = HealthSupport.builder() .addLiveness(HealthChecks.healthChecks()) // <1> @@ -494,21 +485,27 @@ HealthSupport health = HealthSupport.builder() .withData("time", System.currentTimeMillis()) .build()) .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck") // <3> - .state (readyTime.get() != 0 ) - .withData( "time", readyTime.get()) + .status(readyTime.get() != 0 ) + .withData("time", readyTime.get()) + .build()) + .addStartup(() -> HealthCheckResponse.named("StartedCheck") // <4> + .status(readyTime.get() != 0 ) + .withData("time", readyTime.get()) .build()) .build(); ---- <1> Add built-in health-checks. <2> Add a custom liveness check. <3> Add a custom readiness check. +<4> Add a custom started check. [source,bash] -.Build and run the application, then verify the liveness and readiness endpoints: +.Build and run the application, then verify the liveness, readiness, and started endpoints: ---- curl http://localhost:8080/health/live curl http://localhost:8080/health/ready +curl http://localhost:8080/health/started ---- @@ -569,6 +566,14 @@ spec: initialDelaySeconds: 5 // <6> periodSeconds: 2 timeoutSeconds: 3 + startupProbe: + httpGet: + path: /health/started // <7> + port: 8080 + initialDelaySeconds: 8 // <8> + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 --- ---- <1> A service of type `NodePort` that serves the default routes on port `8080`. @@ -577,6 +582,8 @@ spec: <4> The liveness probe configuration. <5> The HTTP endpoint for the readiness probe. <6> The readiness probe configuration. +<7> The HTTP endpoint for the startup probe. +<8> The startup probe configuration. [source,bash] @@ -614,7 +621,7 @@ kubectl delete -f ./health.yaml This guide demonstrated how to use health checks in a Helidon SE application as follows: * Access the default health-check -* Create and use custom readiness and liveness checks +* Create and use custom readiness, liveness, and startup checks * Customize the health-check root path * Integrate Helidon health-check with Kubernetes diff --git a/docs/se/health/01_health.adoc b/docs/se/health/01_health.adoc index 905b5467198..ca3d24b46f5 100644 --- a/docs/se/health/01_health.adoc +++ b/docs/se/health/01_health.adoc @@ -74,7 +74,7 @@ A typical health check combines the statuses of all the dependencies that | Java functional interface representing the logic of a single health check | `org.eclipse.microprofile.health.HealthCheckResponse` -| Result of a health check invocation that contains a state and a description. +| Result of a health check invocation that contains a status and a description. | `org.eclipse.microprofile.health.HealthCheckResponseBuilder` | Builder class to create `HealthCheckResponse` instances @@ -116,7 +116,7 @@ HealthCheck hc = this::exampleHealthCheck; `HealthSupport` is a WebServer service that contains a collection of registered `HealthCheck` instances. When queried, it invokes the registered health check and returns a response with a status code representing the overall - state of the application. + status of the application. [cols="1,5",role="flex, sm7"] .Health status codes @@ -162,12 +162,11 @@ TIP: Balance collecting a lot of information with the need to avoid overloading .JSON response: ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "exampleHealthCheck", - "state": "UP", + "status": "UP", "data": { "time": 1546958376613 } @@ -244,16 +243,15 @@ Accessing the Helidon-provided `/health` endpoint reports the health of your app .JSON response. ---- { - "outcome": "UP", "status": "UP", "checks": [ { "name": "deadlock", - "state": "UP" + "status": "UP" }, { "name": "diskSpace", - "state": "UP", + "status": "UP", "data": { "free": "211.00 GB", "freeBytes": 226563444736, @@ -264,7 +262,7 @@ Accessing the Helidon-provided `/health` endpoint reports the health of your app }, { "name": "heapMemory", - "state": "UP", + "status": "UP", "data": { "free": "215.15 MB", "freeBytes": 225600496, @@ -282,11 +280,3 @@ Accessing the Helidon-provided `/health` endpoint reports the health of your app -=== Strict JSON Output - -The JSON responses shown above contain properties `"status"` and `"outcome"` with the same -values. Helidon reports both of these to maintain backward compatibility with older -versions of MicroProfile Health. This behavior can be disabled by setting -the property `health.backward-compatible` to `false`, in which case only `"status"` -is reported. Future versions of Helidon will drop support for older versions of Health, -so it is recommended to rely on `"status"` instead of `"outcome"` in your applications. diff --git a/docs/se/health/02_health_in_k8s.adoc b/docs/se/health/02_health_in_k8s.adoc index 2df1a3c366c..e00400b5d0c 100644 --- a/docs/se/health/02_health_in_k8s.adoc +++ b/docs/se/health/02_health_in_k8s.adoc @@ -29,10 +29,11 @@ This document describes how to use the Helidon health check API with Kubernetes. Probes is the term used by Kubernetes to describe health checks for containers (link:{kubernetes-probes-url}[Kubernetes documentation]). -There are two types of probes: +There are three types of probes: * `liveness`: Indicates whether the container is running * `readiness`: Indicates whether the container is ready to service requests +* `startup`: Indicates whether the applicaiton in the container has started You can implement probes using the following mechanisms: @@ -42,6 +43,8 @@ You can implement probes using the following mechanisms: A microservice exposed to HTTP traffic will typically implement both the liveness probe and the readiness probe using HTTP requests. +If the microservice takes a significant time to initialize itself, you can also define a startup probe, in which case +Kubernetes does not check liveness or readiness probes until the startup probe returns success. You can configure several parameters for probes. The following are the most relevant parameters: @@ -99,6 +102,12 @@ We recommend the following: * Set `failureThreshold` according to `periodSeconds` in order to accommodate temporary errors. +=== Startup probe + +The startup probe prevents Kubernetes from prematurely checking the other probes if the application takes a long time to start. +Otherwise, Kubernetes might misinterpret a failed liveness or readiness probe and shut down the container when, in fact, the application is still coming up. + + == Troubleshooting probes Failed probes are recorded as events associated with their corresponding pods. From becce06452ba6092f44c0edfc5db07a7c0f4053f Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 16:01:10 -0600 Subject: [PATCH 06/15] Update the SE health example to show start-up support --- .../io/helidon/examples/health/basics/Main.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java index 9b03f3184f0..9f3e8643a75 100644 --- a/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java +++ b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package io.helidon.examples.health.basics; +import java.time.Duration; + import io.helidon.health.HealthSupport; import io.helidon.health.checks.HealthChecks; import io.helidon.webserver.Routing; @@ -28,6 +30,8 @@ */ public final class Main { + private static long serverStartTime; + private Main() { } @@ -37,12 +41,17 @@ private Main() { * @param args not used */ public static void main(String[] args) { + serverStartTime = System.currentTimeMillis(); HealthSupport health = HealthSupport.builder() .addLiveness(HealthChecks.healthChecks()) - .addReadiness((HealthCheck) () -> HealthCheckResponse.named("exampleHealthCheck") + .addReadiness(() -> HealthCheckResponse.named("exampleHealthCheck") .up() .withData("time", System.currentTimeMillis()) .build()) + .addStartup(() -> HealthCheckResponse.named("exampleStartCheck") + .status(isStarted()) + .withData("time", System.currentTimeMillis()) + .build()) .build(); Routing routing = Routing.builder() @@ -61,4 +70,8 @@ public static void main(String[] args) { }); } + + private static boolean isStarted() { + return Duration.ofMillis(System.currentTimeMillis() - serverStartTime).getSeconds() >= 8; + } } From 065b2d5a970481c63d3ed0360787521d6a0f8c91 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 16:33:10 -0600 Subject: [PATCH 07/15] Copyright --- docs/se/health/02_health_in_k8s.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/se/health/02_health_in_k8s.adoc b/docs/se/health/02_health_in_k8s.adoc index e00400b5d0c..b4049954914 100644 --- a/docs/se/health/02_health_in_k8s.adoc +++ b/docs/se/health/02_health_in_k8s.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2019, 2020 Oracle and/or its affiliates. + Copyright (c) 2019, 2021 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From c3568394e275846b39a5cc0a2fcce3cd54c6661a Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 16:43:20 -0600 Subject: [PATCH 08/15] Unused import in example --- .../src/main/java/io/helidon/examples/health/basics/Main.java | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java index 9f3e8643a75..2c180c1e553 100644 --- a/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java +++ b/examples/health/basics/src/main/java/io/helidon/examples/health/basics/Main.java @@ -22,7 +22,6 @@ import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; -import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; /** From 339ddcf70cccad1e689d7a7a95558db24a37c8bb Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Tue, 7 Dec 2021 20:27:10 -0600 Subject: [PATCH 09/15] Correct obsolete reference to 'state' in test --- .../microprofile/messaging/health/MessagingHealthTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/microprofile/messaging/health/src/test/java/io/helidon/microprofile/messaging/health/MessagingHealthTest.java b/microprofile/messaging/health/src/test/java/io/helidon/microprofile/messaging/health/MessagingHealthTest.java index f136f02cfa1..74f63800b97 100644 --- a/microprofile/messaging/health/src/test/java/io/helidon/microprofile/messaging/health/MessagingHealthTest.java +++ b/microprofile/messaging/health/src/test/java/io/helidon/microprofile/messaging/health/MessagingHealthTest.java @@ -53,12 +53,10 @@ * Tests for messaging health: *
{@code
  * {
- *     "outcome": "UP",
  *     "status": "UP",
  *     "checks": [
  *         {
  *             "name": "messaging",
- *             "state": "UP",
  *             "status": "UP",
  *             "data": {
  *                 "test-channel-1": "UP",
@@ -150,7 +148,7 @@ void alivenessWithCancelSignal(SeContainer container) {
 
     private void assertMessagingHealth(HealthCheckResponse.Status rootState, Map channels) {
         JsonObject messaging = getHealthCheck("messaging");
-        assertThat(messaging.getString("state"), equalTo(rootState.name()));
+        assertThat(messaging.getString("status"), equalTo(rootState.name()));
         JsonObject data = messaging.getJsonObject("data");
         channels.forEach((name, state) -> assertThat(data.getString(name), equalTo(state.name())));
     }

From 5d6d04419662c26e99eb23da44348c33a148c6bd Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com" 
Date: Tue, 7 Dec 2021 21:32:28 -0600
Subject: [PATCH 10/15] Remove obsolete expected health response from test

---
 .../src/test/java/io/helidon/tests/bookstore/MainTest.java       | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java
index 811f565003d..c888d06947d 100644
--- a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java
+++ b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java
@@ -409,7 +409,6 @@ private void runMetricsAndHealthTest(String edition, String jsonLibrary, boolean
                 .path("/health")
                 .request(JsonObject.class)
                 .thenAccept(it -> {
-                    assertThat("Checking health outcome", it.getString("outcome"), is("UP"));
                     assertThat("Checking health status", it.getString("status"), is("UP"));
                     // Verify that built-in health checks are disabled in MP according to
                     // 'microprofile-config.properties' setting in bookstore application

From a766f50cf09c56b9b7141efe0efda7ab68f1a745 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com" 
Date: Wed, 8 Dec 2021 11:00:03 -0600
Subject: [PATCH 11/15] Fix typos

---
 docs/mp/guides/04_health.adoc                 |  26 +--
 docs/mp/health/01_introduction.adoc           |   8 +-
 docs/se/guides/04_health.adoc                 | 156 ++++++++++++++----
 docs/se/health/01_health.adoc                 |   4 +-
 docs/se/health/02_health_in_k8s.adoc          |   6 +-
 .../health/HealthMpServiceIT.java             |   4 +-
 6 files changed, 147 insertions(+), 57 deletions(-)

diff --git a/docs/mp/guides/04_health.adoc b/docs/mp/guides/04_health.adoc
index adf21a07689..c6623cb1651 100644
--- a/docs/mp/guides/04_health.adoc
+++ b/docs/mp/guides/04_health.adoc
@@ -18,8 +18,8 @@
 
 = Helidon MP Health Check Guide
 :h1Prefix: MP
-:description: Helidon health-checks
-:keywords: helidon, health-checks, health, check
+:description: Helidon health checks
+:keywords: helidon, health checks, health, check
 :common-page-prefix-inc: ../../shared/common_prereqs/common_prereqs.adoc
 
 This guide describes how to create a sample MicroProfile (MP) project
@@ -191,7 +191,7 @@ import org.eclipse.microprofile.health.Readiness;
 @Readiness // <2>
 @ApplicationScoped
 public class GreetReadinessCheck implements HealthCheck {
-  private AtomicLong readyTime = new AtomicLong(0);
+  private final AtomicLong readyTime = new AtomicLong(0);
 
 
   @Override
@@ -204,7 +204,7 @@ public class GreetReadinessCheck implements HealthCheck {
 
   public void onStartUp(
       @Observes @Initialized(ApplicationScoped.class) Object init) {
-    readyTime = new AtomicLong(System.currentTimeMillis()); // <4>
+    readyTime.set(System.currentTimeMillis()); // <4>
   }
 
   /**
@@ -218,9 +218,9 @@ public class GreetReadinessCheck implements HealthCheck {
 }
 ----
 <1> Include additional imports.
-<2> Annotation indicating that this is a readiness health-check.
+<2> Annotation indicating that this is a readiness health check.
 <3> Build the `HealthCheckResponse` with status `UP` after five seconds, else `DOWN`.
-<4> Initialize the time at startup.
+<4> Record the time at startup.
 
 
 [source,bash]
@@ -301,20 +301,20 @@ import org.eclipse.microprofile.health.Started;
 @Started // <2>
 @ApplicationScoped
 public class GreetStartedCheck implements HealthCheck {
-  private AtomicLong readyTime = new AtomicLong(0);
+  private final AtomicLong readyTime = new AtomicLong(0);
 
 
   @Override
   public HealthCheckResponse call() {
     return HealthCheckResponse.named("StartedCheck")  // <3>
-        .status(isReady())
+        .status(isStarted())
         .withData("time", readyTime.get())
         .build();
   }
 
   public void onStartUp(
       @Observes @Initialized(ApplicationScoped.class) Object init) {
-    readyTime = new AtomicLong(System.currentTimeMillis()); // <4>
+    readyTime.set(System.currentTimeMillis()); // <4>
   }
 
   /**
@@ -328,9 +328,9 @@ public class GreetStartedCheck implements HealthCheck {
 }
 ----
 <1> Include additional imports.
-<2> Annotation indicating that this is a startup health-check.
+<2> Annotation indicating that this is a startup health check.
 <3> Build the `HealthCheckResponse` with status `UP` after eight seconds, else `DOWN`.
-<4> Initialize the time at startup of Helidon; the application will declare itself as started eight seconds later.
+<4> Record the time at startup of Helidon; the application will declare itself as started eight seconds later.
 
 
 [source,bash]
@@ -650,6 +650,6 @@ This guide demonstrated how to use health checks in a Helidon MP application as
 
 Refer to the following references for additional information:
 
-* MicroProfile health-check specification at https://github.com/eclipse/microprofile-health/releases/tag/2.0
-* MicroProfile health-check Javadoc at https://javadoc.io/doc/org.eclipse.microprofile.health/microprofile-health-api/2.0
+* MicroProfile health check specification at https://github.com/eclipse/microprofile-health/releases/tag/2.0
+* MicroProfile health check Javadoc at https://javadoc.io/doc/org.eclipse.microprofile.health/microprofile-health-api/2.0
 * Helidon Javadoc at https://helidon.io/docs/latest/apidocs/index.html?overview-summary.html
diff --git a/docs/mp/health/01_introduction.adoc b/docs/mp/health/01_introduction.adoc
index bd74e60561c..f08e8630136 100644
--- a/docs/mp/health/01_introduction.adoc
+++ b/docs/mp/health/01_introduction.adoc
@@ -87,7 +87,7 @@ such as Kubernetes might react.
 A MicroProfile-compliant service reports its health via known REST endpoints. Helidon MP
 provides these endpoints automatically as part of every MP microservice.
 
-External management tools (or `curl` or browsers) retrieve health checks using the REST endpoints in he following table which summarizes the types of health checks in MicroProfile Health.
+External management tools (or `curl` or browsers) retrieve health checks using the REST endpoints in the following table which summarizes the types of health checks in MicroProfile Health.
 
 .Types of Health Checks
 |===
@@ -107,7 +107,7 @@ microservice reports itself as ready.
 |startup
 |whether the microservice has initialized to the point where liveness and readiness checks might pass
 |`/health/started`
-|Treats the instance as still starting up; does not check liveness or readiness until the startup [robe reports success or times out according to its configuration.
+|Treats the instance as still starting up; does not check liveness or readiness until the startup probe reports success or times out according to its configuration.
 |===
 
 === Built-in and Custom Health Checks
@@ -122,8 +122,8 @@ and viewing the response.
 
 ==== Custom Health Checks
 Add your own liveness, readiness, or startup checks by adding a Java class for each check.
-Each custom check must implement the `HealthCheck` interface, and you add either the `@Liveness`, `@Startup`, or
- `@Readiness` annotation to the class.
+Each custom check must implement the `HealthCheck` interface, and you add either the `@Liveness`,
+ `@Readiness`, or `@Startup` annotation to the class.
 
 === Next Steps
 Add custom health checks to your own microservices.
diff --git a/docs/se/guides/04_health.adoc b/docs/se/guides/04_health.adoc
index 70de062eeec..74c01d8109e 100644
--- a/docs/se/guides/04_health.adoc
+++ b/docs/se/guides/04_health.adoc
@@ -18,12 +18,12 @@
 
 = Helidon SE Health Check Guide
 :h1Prefix: SE
-:description: Helidon health-checks
-:keywords: helidon, health-check, health, check
+:description: Helidon health checks
+:keywords: helidon, health check, health check, health, check
 :common-page-prefix-inc: ../../shared/common_prereqs/common_prereqs.adoc
 
 This guide describes how to create a sample Helidon SE project
-that can be used to run some basic examples using both built-in and custom health-checks.
+that can be used to run some basic examples using both built-in and custom health checks.
 
 == What You Need
 
@@ -51,17 +51,17 @@ mvn -U archetype:generate -DinteractiveMode=false \
 === Using the Built-In Health Checks
 
 Helidon has a set of built-in health checks that can be optionally enabled to report various
- health-check statuses that are commonly used:
+ health check statuses that are commonly used:
 
 * deadlock detection
 * available disk space
 * available heap memory
 
-The following example will demonstrate how to use the built-in health-checks.  These examples are all executed
+The following example will demonstrate how to use the built-in health checks.  These examples are all executed
 from the root directory of your project (helidon-quickstart-se).
 
 [source,xml]
-.Notice that the built-in health-check dependency is already in the project's pom.xml file:
+.Notice that the built-in health check dependency is already in the project's pom.xml file:
 ----
 
     io.helidon.health
@@ -83,7 +83,7 @@ private static Routing createRouting(Config config) {
       .build();
 }
 ----
-<1> Add built-in health-checks (requires the `helidon-health-checks`
+<1> Add built-in health checks (requires the `helidon-health-checks`
  dependency).
 <2> Register the created health support with web server routing (adds the
 `/health` endpoint).
@@ -149,7 +149,7 @@ health check and returns a response with a status code representing the overall
 state of the application.
 
 [source,xml]
-.Notice the custom health-checks dependency is already in the project's pom.xml file:
+.Notice the custom health checks dependency is already in the project's pom.xml file:
 ----
 
     io.helidon.health
@@ -193,9 +193,9 @@ curl http://localhost:8080/health
 }
 ----
 
-=== Custom Readiness Health Check
+=== Custom Readiness Health Checks
 
-You can add a readiness check to indicate that the application is ready to be used.  In this
+You can add readiness checks to indicate that the application is ready to be used.  In this
 example, the server will wait five seconds before it becomes ready.
 
 [source,java]
@@ -237,7 +237,7 @@ HealthSupport health = HealthSupport.builder()
       .withData("time", System.currentTimeMillis())
       .build())
   .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
-      .state (readyTime.get() != 0 )
+      .status(readyTime.get() != 0 )
       .withData( "time", readyTime.get())
       .build()) // <1>
   .build();
@@ -245,7 +245,7 @@ HealthSupport health = HealthSupport.builder()
 <1> Add the readiness check.
 
 [source,bash]
-.Build and run the application.  Issue the curl command with -v within five seconds and you see the application is not ready:
+.Build and run the application.  Issue the `curl` command with -v within five seconds and you see the application is not ready:
 ----
 curl -v  http://localhost:8080/health/ready
 ----
@@ -298,17 +298,98 @@ curl -v http://localhost:8080/health/ready
 ----
 <1> The HTTP status is `200` indicating that the application is ready.
 
+=== Custom Startup Health Checks
+
+You can create custom startup health checks to indicate when the application has fully started and, therefore, when the readiness and liveness checks are meaningful.
+
+This example reuses the `readyTime` field added above for the custom readiness check and adds a startup check that waits three additional seconds past the "ready" time before declaring the application started.
+
+[source,java]
+.Add a startup check to the `HealhSupport` builder in the `Main.createRouting` method:
+----
+HealthSupport health = HealthSupport.builder()
+  .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
+      .up()
+      .withData("time", System.currentTimeMillis())
+      .build())
+  .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
+      .status(readyTime.get() != 0 )
+      .withData("time", readyTime.get())
+      .build())
+  .addStartup(() -> HealthCheckResponse.named("StartupCheck") // <1>
+      .status(readyTime.get() != 0
+              && Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 3)
+      .withData("time", readyTime.get())
+      .build())
+  .build();
+----
+<1> Add the startup check.
 
-When using the health-check URLs, you can get the following health-check data
+[source,bash]
+.Build and run the application.  Issue the `curl` command with -v within eight seconds and you see the application is not reported as started:
+----
+curl -v  http://localhost:8080/health/started
+----
+
+[source,json]
+.HTTP response:
+----
+...
+< HTTP/1.1 503 Service Unavailable // <1>
+...
+{
+  "status": "DOWN",
+  "checks": [
+    {
+      "name": "StartupCheck",
+      "status": "DOWN",
+      "data": {
+        "time": 1566243562097
+      }
+    }
+  ]
+}
+----
+<1> The HTTP status is `503` since the application is not started.
+
+[source,bash]
+.After eight seconds you will see the application is started:
+----
+curl -v http://localhost:8080/health/started
+----
+
+[source,json]
+.JSON response:
+----
+...
+< HTTP/1.1 200 OK // <1>
+...
+{
+  "status": "UP",
+  "checks": [
+    {
+      "name": "StartupCheck",
+      "status": "UP",
+      "data": {
+        "time": 1566243562097
+      }
+    }
+  ]
+}
+----
+<1> The HTTP status is `200` indicating that the application is started.
+
+
+When using the health check URLs, you can get the following health check data
 
 * liveness only - http://localhost:8080/health/live
 * readiness only -  http://localhost:8080/health/ready
-* start-up only - http://localhost:8080/health/started
+* startup only - http://localhost:8080/health/started
 * all -  http://localhost:8080/health
 
 
 [source,bash]
-.Get all of liveness, readiness, and start-up data from a single query:
+.Get all of liveness, readiness, and startup data from a single query:
 ----
 curl http://localhost:8080/health
 ----
@@ -332,6 +413,13 @@ curl http://localhost:8080/health
       "data": {
         "time,": 1566244093012
       }
+    },
+    {
+      "name": "StartupCheck",
+      "status": "UP",
+      "data": {
+        "time": 1566244093012
+      }
     }
   ]
 }
@@ -339,10 +427,10 @@ curl http://localhost:8080/health
 
 === Combine Built-In and Custom Health Checks
 
-You can combine built-in and custom health-checks using the same `HealthSupport` builder.
+You can combine built-in and custom health checks using the same `HealthSupport` builder.
 
 [source,java]
-.Register a custom health-check in the `Main.createRouting` method:
+.Register a custom health check in the `Main.createRouting` method:
 ----
 HealthSupport health = HealthSupport.builder()
     .addLiveness(HealthChecks.healthChecks())  // <1>
@@ -354,16 +442,17 @@ HealthSupport health = HealthSupport.builder()
       .status(readyTime.get() != 0)
       .withData("time", readyTime.get())
       .build())
-    .addStartup(() -> HealthCheckResponse.named("StartedCheck")
-      .status(readyTime.get() != 0)
+    .addStartup(() -> HealthCheckResponse.named("StartupCheck")
+      .status(readyTime.get() != 0
+              && Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 3)
       .withData("time", readyTime.get())
       .build())
     .build();
 ----
-<1> Add the built-in health-checks back to `HealthSupport` builder.
+<1> Add the built-in health checks back to `HealthSupport` builder.
 
 [source,bash]
-.Build and run the application, then verify the health endpoint.  You will see both the built-in and custom health-check data:
+.Build and run the application, then verify the health endpoint.  You will see both the built-in and custom health check data:
 ----
 curl http://localhost:8080/health
 ----
@@ -388,7 +477,7 @@ curl http://localhost:8080/health
         "time,": 1566245527620
       },
     {
-      "name": "StartedCheck",
+      "name": "StartupCheck",
       "status": "UP",
       "data": {
         "time,": 1566245527620
@@ -430,11 +519,11 @@ curl http://localhost:8080/health
 === Custom Health Check URL Path
 
 You can use a custom URL path for heath checks by setting the `WebContext`.  In the following example, only
-the liveness URL is changed, but you can do the same for the readiness and default
-health-checks.
+the liveness URL is changed, but you can do the same for the readiness, startup, and default
+health checks.
 
 [source,java]
-.Register a custom URL path with the custom health-check in the `Main.createRouting` method:
+.Register a custom URL path with the custom health check in the `Main.createRouting` method:
 ----
 HealthSupport health = HealthSupport.builder()
     .webContext("/probe/live")// <1>
@@ -476,7 +565,7 @@ The following example shows how to integrate the Helidon health API in an applic
 health endpoints for the Kubernetes liveness, readiness, and startup probes.
 
 [source,java]
-.Change the `HealthSupport` builder in the `Main.createRouting` method to use the built-in liveness checks and custom liveness, readiness, and started checks:
+.Change the `HealthSupport` builder in the `Main.createRouting` method to use the built-in liveness checks and custom liveness, readiness, and startup checks:
 ----
 HealthSupport health = HealthSupport.builder()
     .addLiveness(HealthChecks.healthChecks()) // <1>
@@ -488,16 +577,17 @@ HealthSupport health = HealthSupport.builder()
       .status(readyTime.get() != 0 )
       .withData("time", readyTime.get())
       .build())
-    .addStartup(() -> HealthCheckResponse.named("StartedCheck")  // <4>
-      .status(readyTime.get() != 0 )
+    .addStartup(() -> HealthCheckResponse.named("StartupCheck")  // <4>
+      .status(readyTime.get() != 0
+              && Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 3)
       .withData("time", readyTime.get())
       .build())
     .build();
 ----
-<1> Add built-in health-checks.
+<1> Add built-in health checks.
 <2> Add a custom liveness check.
 <3> Add a custom readiness check.
-<4> Add a custom started check.
+<4> Add a custom startup check.
 
 
 [source,bash]
@@ -620,10 +710,10 @@ kubectl delete -f ./health.yaml
 === Summary
 This guide demonstrated how to use health checks in a Helidon SE application as follows:
 
-* Access the default health-check
+* Access the default health check
 * Create and use custom readiness, liveness, and startup checks
-* Customize the health-check root path
-* Integrate Helidon health-check with Kubernetes
+* Customize the health check root path
+* Integrate Helidon health check with Kubernetes
 
 Refer to the following reference for additional information:
 
diff --git a/docs/se/health/01_health.adoc b/docs/se/health/01_health.adoc
index ca3d24b46f5..f0e35ef8edc 100644
--- a/docs/se/health/01_health.adoc
+++ b/docs/se/health/01_health.adoc
@@ -19,7 +19,7 @@
 = Health Checks
 :h1Prefix: SE
 :description: Helidon health checks
-:keywords: helidon, health-checks, health, check
+:keywords: helidon, health checks, health, check
 :javadoc-base-url-api: {javadoc-base-url}io.helidon.health.checks/io/helidon/health/checks
 :feature-name: Health Checks
 :common-deps-page-prefix-inc: ../../shared/dependencies/common_shared.adoc
@@ -175,7 +175,7 @@ TIP: Balance collecting a lot of information with the need to avoid overloading
 }
 ----
 
-=== Built-in health-checks
+=== Built-in health checks
 
 You can use Helidon-provided health checks to report various
  common health check statuses:
diff --git a/docs/se/health/02_health_in_k8s.adoc b/docs/se/health/02_health_in_k8s.adoc
index b4049954914..45fe96f3d9c 100644
--- a/docs/se/health/02_health_in_k8s.adoc
+++ b/docs/se/health/02_health_in_k8s.adoc
@@ -31,9 +31,9 @@ Probes is the term used by Kubernetes to describe health checks for containers
 
 There are three types of probes:
 
-* `liveness`: Indicates whether the container is running
-* `readiness`: Indicates whether the container is ready to service requests
-* `startup`: Indicates whether the applicaiton in the container has started
+* _liveness_: Indicates whether the container is running
+* _readiness_: Indicates whether the container is ready to service requests
+* _startup_: Indicates whether the application in the container has started
 
 You can implement probes using the following mechanisms:
 
diff --git a/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java b/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java
index f17d99b0f64..b6886fdb249 100644
--- a/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java
+++ b/microprofile/health/src/test/java/io/helidon/microprofile/health/HealthMpServiceIT.java
@@ -146,7 +146,7 @@ public HealthCheckResponse call() {
     }
 
     /**
-     * A Test {@link HealthCheck} bean for start-up that should be discovered
+     * A Test {@link HealthCheck} bean for startup that should be discovered
      * by CDI and added to the health check endpoint.
      */
     @Startup
@@ -206,7 +206,7 @@ public List livenessChecks() {
 
     /**
      * A test {@link HealthCheckProvider} bean that should be discovered
-     * by the service loader and its provided start-up {@link HealthCheck}s added
+     * by the service loader and its provided startup {@link HealthCheck}s added
      * to the health check endpoint.
      */
     public static class HealthCheckProviderThree

From dea48f7f193797ae79be507892cda77ee147ff89 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com" 
Date: Thu, 9 Dec 2021 13:21:39 -0600
Subject: [PATCH 12/15] Update HealthSupport to use common REST service
 framework

---
 health/health/pom.xml                         |   8 +-
 .../java/io/helidon/health/HealthSupport.java | 116 +++++++++---------
 health/health/src/main/java/module-info.java  |   2 +
 3 files changed, 69 insertions(+), 57 deletions(-)

diff --git a/health/health/pom.xml b/health/health/pom.xml
index 015e62eeba4..e9eac30aec9 100644
--- a/health/health/pom.xml
+++ b/health/health/pom.xml
@@ -60,8 +60,12 @@
             
         
         
-            io.helidon.webserver
-            helidon-webserver-cors
+            io.helidon.service-common
+            helidon-service-common-rest
+        
+        
+            io.helidon.config
+            helidon-config-metadata
         
         
             org.junit.jupiter
diff --git a/health/health/src/main/java/io/helidon/health/HealthSupport.java b/health/health/src/main/java/io/helidon/health/HealthSupport.java
index d7bcd9c66fd..58b8df012e9 100644
--- a/health/health/src/main/java/io/helidon/health/HealthSupport.java
+++ b/health/health/src/main/java/io/helidon/health/HealthSupport.java
@@ -24,7 +24,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -36,16 +35,16 @@
 import io.helidon.common.http.Http;
 import io.helidon.common.reactive.Single;
 import io.helidon.config.Config;
+import io.helidon.config.metadata.Configured;
+import io.helidon.config.metadata.ConfiguredOption;
 import io.helidon.faulttolerance.Async;
 import io.helidon.faulttolerance.Timeout;
 import io.helidon.media.common.MessageBodyWriter;
 import io.helidon.media.jsonp.JsonpSupport;
+import io.helidon.servicecommon.rest.HelidonRestServiceSupport;
 import io.helidon.webserver.Routing;
 import io.helidon.webserver.ServerRequest;
 import io.helidon.webserver.ServerResponse;
-import io.helidon.webserver.Service;
-import io.helidon.webserver.cors.CorsEnabledServiceHelper;
-import io.helidon.webserver.cors.CrossOriginConfig;
 
 import jakarta.json.Json;
 import jakarta.json.JsonArrayBuilder;
@@ -57,12 +56,10 @@
 import org.eclipse.microprofile.health.HealthCheckResponse;
 import org.eclipse.microprofile.health.HealthCheckResponse.Status;
 
-import static io.helidon.webserver.cors.CorsEnabledServiceHelper.CORS_CONFIG_KEY;
-
 /**
  * Health check support for integration with webserver, to expose the health endpoint.
  */
-public final class HealthSupport implements Service {
+public final class HealthSupport extends HelidonRestServiceSupport {
     /**
      * Default web context root of the Health check endpoint.
      */
@@ -75,7 +72,6 @@ public final class HealthSupport implements Service {
     private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
 
     private final boolean enabled;
-    private final String webContext;
     private final List allChecks = new LinkedList<>();
     private final List livenessChecks = new LinkedList<>();
     private final List readinessChecks = new LinkedList<>();
@@ -83,15 +79,13 @@ public final class HealthSupport implements Service {
     private final boolean includeAll;
     private final Set includedHealthChecks;
     private final Set excludedHealthChecks;
-    private final CorsEnabledServiceHelper corsEnabledServiceHelper;
     private final MessageBodyWriter jsonpWriter = JsonpSupport.writer();
     private final Timeout timeout;
     private final Async async;
 
     private HealthSupport(Builder builder) {
+        super(LOGGER, builder, SERVICE_NAME);
         this.enabled = builder.enabled;
-        this.webContext = builder.webContext;
-        corsEnabledServiceHelper = CorsEnabledServiceHelper.create(SERVICE_NAME, builder.crossOriginConfig);
 
         if (enabled) {
             collectNonexcludedChecks(builder, builder.allChecks, allChecks::add);
@@ -120,11 +114,16 @@ public void update(Routing.Rules rules) {
             // do not register anything if health check is disabled
             return;
         }
-        rules.any(webContext, corsEnabledServiceHelper.processor())
-                .get(webContext, this::callAll)
-                .get(webContext + "/live", this::callLiveness)
-                .get(webContext + "/ready", this::callReadiness)
-                .get(webContext + "/started", this::callStartup);
+        configureEndpoint(rules, rules);
+    }
+
+    @Override
+    protected void postConfigureEndpoint(Routing.Rules defaultRules, Routing.Rules serviceEndpointRoutingRules) {
+        serviceEndpointRoutingRules
+                .get(context(), this::callAll)
+                .get(context() + "/live", this::callLiveness)
+                .get(context() + "/ready", this::callReadiness)
+                .get(context() + "/started", this::callStartup);
     }
 
     private static void collectNonexcludedChecks(Builder builder, List checks, Consumer adder) {
@@ -270,7 +269,40 @@ public static HealthSupport create(Config config) {
     /**
      * Fluent API builder for {@link io.helidon.health.HealthSupport}.
      */
-    public static final class Builder implements io.helidon.common.Builder {
+    @Configured(prefix = Builder.HEALTH_CONFIG_KEY)
+    public static final class Builder extends HelidonRestServiceSupport.Builder {
+
+        /**
+         * Config key for the {@code health} section.
+         */
+        public static final String HEALTH_CONFIG_KEY = "health";
+
+        /**
+         * Config key within the config {@code health} section controlling whether health is enabled.
+         */
+        public static final String ENABLED_CONFIG_KEY = "enabled";
+
+        /**
+         * Config key within the config {@code health} section indicating health checks to include.
+         */
+        public static final String INCLUDE_CONFIG_KEY = "include";
+
+        /**
+         * Config key within the config {@code health} section indicating health checks to exclude.
+         */
+        public static final String EXCLUDE_CONFIG_KEY = "exclude";
+
+        /**
+         * Config key within the config {@code health} section indicating health check implementation classes to exclude.
+         */
+        public static final String EXCLUDE_CLASSES_CONFIG_KEY = "exclude-classes";
+
+        /**
+         * Config key within the config {@code health} section controlling the timeout for calculating the health report when
+         * clients access the health endpoint.
+         */
+        public static final String TIMEOUT_CONFIG_KEY = "timeout-millis";
+
         // 10 seconds
         private static final long DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
         private final List allChecks = new LinkedList<>();
@@ -281,12 +313,11 @@ public static final class Builder implements io.helidon.common.Builder> excludedClasses = new HashSet<>();
         private final Set includedHealthChecks = new HashSet<>();
         private final Set excludedHealthChecks = new HashSet<>();
-        private String webContext = DEFAULT_WEB_CONTEXT;
         private boolean enabled = true;
-        private CrossOriginConfig crossOriginConfig;
         private long timeoutMillis = DEFAULT_TIMEOUT_MILLIS;
 
         private Builder() {
+            super(Builder.class, DEFAULT_WEB_CONTEXT);
         }
 
         @Override
@@ -294,21 +325,6 @@ public HealthSupport build() {
             return new HealthSupport(this);
         }
 
-        /**
-         * Path under which to register health check endpoint on the web server.
-         *
-         * @param path webContext to use, defaults to
-         * @return updated builder instance
-         */
-        public Builder webContext(String path) {
-            if (path.startsWith("/")) {
-                this.webContext = path;
-            } else {
-                this.webContext = "/" + path;
-            }
-            return this;
-        }
-
         /**
          * Add a health check to a white list (in case {@link #includeAll} is set to {@code false}.
          *
@@ -326,6 +342,7 @@ public Builder addIncluded(String healthCheckName) {
          * @param names names of health checks to include
          * @return updated builder instance
          */
+        @ConfiguredOption(key = INCLUDE_CONFIG_KEY)
         public Builder addIncluded(Collection names) {
             if (null == names) {
                 return this;
@@ -355,6 +372,7 @@ public Builder addExcluded(String healthCheckName) {
          * @param names names of health checks to exclude
          * @return updated builder instance
          */
+        @ConfiguredOption(key = EXCLUDE_CONFIG_KEY)
         public Builder addExcluded(Collection names) {
             if (null == names) {
                 return this;
@@ -370,15 +388,12 @@ public Builder addExcluded(Collection names) {
          * @return updated builder instance
          */
         public Builder config(Config config) {
-            config.get("enabled").asBoolean().ifPresent(this::enabled);
-            config.get("web-context").asString().ifPresent(this::webContext);
-            config.get("include").asList(String.class).ifPresent(list -> list.forEach(this::addIncluded));
-            config.get("exclude").asList(String.class).ifPresent(list -> list.forEach(this::addExcluded));
-            config.get("exclude-classes").asList(Class.class).ifPresent(list -> list.forEach(this::addExcludedClass));
-            config.get("timeout-millis").asLong().ifPresent(this::timeoutMillis);
-            config.get(CORS_CONFIG_KEY)
-                    .as(CrossOriginConfig::create)
-                    .ifPresent(this::crossOriginConfig);
+            super.config(config);
+            config.get(ENABLED_CONFIG_KEY).asBoolean().ifPresent(this::enabled);
+            config.get(INCLUDE_CONFIG_KEY).asList(String.class).ifPresent(list -> list.forEach(this::addIncluded));
+            config.get(EXCLUDE_CONFIG_KEY).asList(String.class).ifPresent(list -> list.forEach(this::addExcluded));
+            config.get(EXCLUDE_CLASSES_CONFIG_KEY).asList(Class.class).ifPresent(list -> list.forEach(this::addExcludedClass));
+            config.get(TIMEOUT_CONFIG_KEY).asLong().ifPresent(this::timeoutMillis);
             return this;
         }
 
@@ -393,6 +408,7 @@ private void timeoutMillis(long aLong) {
          * @param unit timeout time unit
          * @return updated builder instance
          */
+        @ConfiguredOption(key = TIMEOUT_CONFIG_KEY, description = "health endpoint timeout (ms)")
         public Builder timeout(long timeout, TimeUnit unit) {
             timeoutMillis(unit.toMillis(timeout));
             return this;
@@ -405,6 +421,7 @@ public Builder timeout(long timeout, TimeUnit unit) {
          * @param aClass class to ignore (any health check instance of this class will be ignored)
          * @return updated builder instance
          */
+        @ConfiguredOption(key = EXCLUDE_CLASSES_CONFIG_KEY, kind = ConfiguredOption.Kind.LIST)
         public Builder addExcludedClass(Class aClass) {
             this.excludedClasses.add(aClass);
             return this;
@@ -484,22 +501,11 @@ public Builder addStartup(Collection healthChecks) {
          * @param enabled whether to enable the health support (defaults to {@code true})
          * @return updated builder instance
          */
+        @ConfiguredOption(key = ENABLED_CONFIG_KEY)
         public Builder enabled(boolean enabled) {
             this.enabled = enabled;
             return this;
         }
-
-        /**
-         * Set the CORS config from the specified {@code CrossOriginConfig} object.
-         *
-         * @param crossOriginConfig {@code CrossOriginConfig} containing CORS set-up
-         * @return updated builder instance
-         */
-        public Builder crossOriginConfig(CrossOriginConfig crossOriginConfig) {
-            Objects.requireNonNull(crossOriginConfig, "CrossOriginConfig must be non-null");
-            this.crossOriginConfig = crossOriginConfig;
-            return this;
-        }
     }
 
     private static final class HcResponse {
diff --git a/health/health/src/main/java/module-info.java b/health/health/src/main/java/module-info.java
index 882af56637e..7e83a9ff2f6 100644
--- a/health/health/src/main/java/module-info.java
+++ b/health/health/src/main/java/module-info.java
@@ -23,6 +23,8 @@
     requires io.helidon.common;
     requires transitive microprofile.health.api;
     requires io.helidon.webserver;
+    requires io.helidon.servicecommon.rest;
+    requires io.helidon.config.metadata;
     requires io.helidon.webserver.cors;
     requires io.helidon.media.jsonp;
     requires jakarta.json;

From b3e25511bb89a29924a176c6958b02c0d524d430 Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com" 
Date: Thu, 9 Dec 2021 13:22:01 -0600
Subject: [PATCH 13/15] Update health CDI extension to use common REST service
 CDI framework

---
 microprofile/health/pom.xml                   |  4 ++++
 .../health/HealthCdiExtension.java            | 19 ++++++++++++++++---
 .../health/src/main/java/module-info.java     |  1 +
 3 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/microprofile/health/pom.xml b/microprofile/health/pom.xml
index 21204ab44ea..4eace47c0b7 100644
--- a/microprofile/health/pom.xml
+++ b/microprofile/health/pom.xml
@@ -56,6 +56,10 @@
             io.helidon.health
             helidon-health-common
         
+        
+            io.helidon.service-common
+            helidon-service-common-rest-cdi
+        
         
             org.junit.jupiter
             junit-jupiter-api
diff --git a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
index bdb2750b58e..45c4c2b7d9f 100644
--- a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
+++ b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
@@ -29,6 +29,7 @@
 import io.helidon.health.HealthSupport;
 import io.helidon.health.common.BuiltInHealthCheck;
 import io.helidon.microprofile.server.RoutingBuilders;
+import io.helidon.servicecommon.restcdi.HelidonRestCdiExtension;
 
 import jakarta.annotation.Priority;
 import jakarta.enterprise.context.ApplicationScoped;
@@ -36,7 +37,7 @@
 import jakarta.enterprise.event.Observes;
 import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
 import jakarta.enterprise.inject.spi.CDI;
-import jakarta.enterprise.inject.spi.Extension;
+import jakarta.enterprise.inject.spi.ProcessManagedBean;
 import org.eclipse.microprofile.config.ConfigProvider;
 import org.eclipse.microprofile.health.HealthCheck;
 import org.eclipse.microprofile.health.Liveness;
@@ -48,7 +49,7 @@
 /**
  * Health extension.
  */
-public class HealthCdiExtension implements Extension {
+public class HealthCdiExtension extends HelidonRestCdiExtension {
     private static final BuiltInHealthCheck BUILT_IN_HEALTH_CHECK_LITERAL = new BuiltInHealthCheck() {
         @Override
         public Class annotationType() {
@@ -58,6 +59,13 @@ public Class annotationType() {
 
     private static final Logger LOGGER = Logger.getLogger(HealthCdiExtension.class.getName());
 
+    /**
+     * Creates a new instance of the health CDI extension.
+     */
+    public HealthCdiExtension() {
+        super(LOGGER, HealthSupport::create, HealthSupport.Builder.HEALTH_CONFIG_KEY);
+    }
+
     void registerProducers(@Observes BeforeBeanDiscovery bbd) {
         bbd.addAnnotatedType(JvmRuntimeProducers.class, "health.JvmRuntimeProducers")
                 .add(ApplicationScoped.Literal.INSTANCE);
@@ -65,7 +73,7 @@ void registerProducers(@Observes BeforeBeanDiscovery bbd) {
 
     void registerHealth(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class) Object adv) {
         org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();
-        Config helidonConfig = MpConfig.toHelidonConfig(config).get("health");
+        Config helidonConfig = MpConfig.toHelidonConfig(config).get(HealthSupport.Builder.HEALTH_CONFIG_KEY);
 
         if (!config.getOptionalValue("health.enabled", Boolean.class).orElse(true)) {
             LOGGER.finest("Health support is disabled in configuration");
@@ -113,4 +121,9 @@ void registerHealth(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(Applic
                 .routingBuilder()
                 .register(builder.build());
     }
+
+    @Override
+    protected void processManagedBean(ProcessManagedBean processManagedBean) {
+        // Annotated sites are handled in registerHealth.
+    }
 }
diff --git a/microprofile/health/src/main/java/module-info.java b/microprofile/health/src/main/java/module-info.java
index 12ed504a51a..d15b02fe1a8 100644
--- a/microprofile/health/src/main/java/module-info.java
+++ b/microprofile/health/src/main/java/module-info.java
@@ -25,6 +25,7 @@
     requires io.helidon.common.serviceloader;
     requires io.helidon.health;
     requires io.helidon.health.common;
+    requires io.helidon.servicecommon.restcdi;
     requires io.helidon.microprofile.server;
 
     requires jakarta.cdi;

From ddd8e484df90c2cc49465e2f8defc72ed8488eba Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com" 
Date: Tue, 14 Dec 2021 16:10:55 -0600
Subject: [PATCH 14/15] Clean up conflicting logic now that HealthCdiExtension
 extends HelidonRestCdiExtension

---
 .../java/io/helidon/health/HealthSupport.java | 16 ++++------
 .../health/HealthCdiExtension.java            | 32 +++++++++++++------
 2 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/health/health/src/main/java/io/helidon/health/HealthSupport.java b/health/health/src/main/java/io/helidon/health/HealthSupport.java
index 58b8df012e9..054b0d38874 100644
--- a/health/health/src/main/java/io/helidon/health/HealthSupport.java
+++ b/health/health/src/main/java/io/helidon/health/HealthSupport.java
@@ -110,20 +110,18 @@ private HealthSupport(Builder builder) {
 
     @Override
     public void update(Routing.Rules rules) {
-        if (!enabled) {
-            // do not register anything if health check is disabled
-            return;
-        }
         configureEndpoint(rules, rules);
     }
 
     @Override
     protected void postConfigureEndpoint(Routing.Rules defaultRules, Routing.Rules serviceEndpointRoutingRules) {
-        serviceEndpointRoutingRules
-                .get(context(), this::callAll)
-                .get(context() + "/live", this::callLiveness)
-                .get(context() + "/ready", this::callReadiness)
-                .get(context() + "/started", this::callStartup);
+        if (enabled) {
+            serviceEndpointRoutingRules
+                    .get(context(), this::callAll)
+                    .get(context() + "/live", this::callLiveness)
+                    .get(context() + "/ready", this::callReadiness)
+                    .get(context() + "/started", this::callStartup);
+        }
     }
 
     private static void collectNonexcludedChecks(Builder builder, List checks, Consumer adder) {
diff --git a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
index 45c4c2b7d9f..07b73093096 100644
--- a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
+++ b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
@@ -20,6 +20,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.ServiceLoader;
+import java.util.function.Function;
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
 
@@ -29,12 +30,15 @@
 import io.helidon.health.HealthSupport;
 import io.helidon.health.common.BuiltInHealthCheck;
 import io.helidon.microprofile.server.RoutingBuilders;
+import io.helidon.microprofile.server.ServerCdiExtension;
 import io.helidon.servicecommon.restcdi.HelidonRestCdiExtension;
+import io.helidon.webserver.Routing;
 
 import jakarta.annotation.Priority;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.enterprise.context.Initialized;
 import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.inject.spi.BeanManager;
 import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
 import jakarta.enterprise.inject.spi.CDI;
 import jakarta.enterprise.inject.spi.ProcessManagedBean;
@@ -63,7 +67,7 @@ public Class annotationType() {
      * Creates a new instance of the health CDI extension.
      */
     public HealthCdiExtension() {
-        super(LOGGER, HealthSupport::create, HealthSupport.Builder.HEALTH_CONFIG_KEY);
+        super(LOGGER, healthSupportFactory, HealthSupport.Builder.HEALTH_CONFIG_KEY);
     }
 
     void registerProducers(@Observes BeforeBeanDiscovery bbd) {
@@ -71,14 +75,23 @@ void registerProducers(@Observes BeforeBeanDiscovery bbd) {
                 .add(ApplicationScoped.Literal.INSTANCE);
     }
 
-    void registerHealth(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class) Object adv) {
-        org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();
-        Config helidonConfig = MpConfig.toHelidonConfig(config).get(HealthSupport.Builder.HEALTH_CONFIG_KEY);
+    @Override
+    protected Routing.Builder registerService(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class)
+                                                      Object adv,
+                                              BeanManager bm,
+                                              ServerCdiExtension server) {
+        Routing.Builder defaultRouting = super.registerService(adv, bm, server);
 
+        org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();
         if (!config.getOptionalValue("health.enabled", Boolean.class).orElse(true)) {
             LOGGER.finest("Health support is disabled in configuration");
-            return;
         }
+        return defaultRouting;
+    }
+
+    private static final Function healthSupportFactory = (Config helidonConfig) -> {
+
+        org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();
 
         HealthSupport.Builder builder = HealthSupport.builder()
                 .config(helidonConfig);
@@ -117,13 +130,14 @@ void registerHealth(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(Applic
                     healthCheckProvider.startupChecks().forEach(builder::addStartup);
                 });
 
-        RoutingBuilders.create(helidonConfig)
-                .routingBuilder()
-                .register(builder.build());
-    }
+        return builder.build();
+        };
+
 
     @Override
     protected void processManagedBean(ProcessManagedBean processManagedBean) {
         // Annotated sites are handled in registerHealth.
     }
+
+
 }

From 3a09f94d1779977be7ca94291741a54dcfa5bc4e Mon Sep 17 00:00:00 2001
From: "tim.quinn@oracle.com" 
Date: Tue, 14 Dec 2021 16:14:01 -0600
Subject: [PATCH 15/15] Fix style errors

---
 .../io/helidon/microprofile/health/HealthCdiExtension.java  | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
index 07b73093096..4a6c45edaa1 100644
--- a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
+++ b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java
@@ -26,10 +26,8 @@
 
 import io.helidon.common.serviceloader.HelidonServiceLoader;
 import io.helidon.config.Config;
-import io.helidon.config.mp.MpConfig;
 import io.helidon.health.HealthSupport;
 import io.helidon.health.common.BuiltInHealthCheck;
-import io.helidon.microprofile.server.RoutingBuilders;
 import io.helidon.microprofile.server.ServerCdiExtension;
 import io.helidon.servicecommon.restcdi.HelidonRestCdiExtension;
 import io.helidon.webserver.Routing;
@@ -67,7 +65,7 @@ public Class annotationType() {
      * Creates a new instance of the health CDI extension.
      */
     public HealthCdiExtension() {
-        super(LOGGER, healthSupportFactory, HealthSupport.Builder.HEALTH_CONFIG_KEY);
+        super(LOGGER, HEALTH_SUPPORT_FACTORY, HealthSupport.Builder.HEALTH_CONFIG_KEY);
     }
 
     void registerProducers(@Observes BeforeBeanDiscovery bbd) {
@@ -89,7 +87,7 @@ protected Routing.Builder registerService(@Observes @Priority(LIBRARY_BEFORE + 1
         return defaultRouting;
     }
 
-    private static final Function healthSupportFactory = (Config helidonConfig) -> {
+    private static final Function HEALTH_SUPPORT_FACTORY = (Config helidonConfig) -> {
 
         org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();