From 5c119a685a99f490e4eb1ddc19b9a0b2f8be783b Mon Sep 17 00:00:00 2001 From: xstefank Date: Mon, 3 Feb 2025 13:48:55 +0100 Subject: [PATCH] Introduce HealthReporter interface --- api/pom.xml | 4 + .../eclipse/microprofile/health/Health.java | 56 ++++++++ .../microprofile/health/HealthReporter.java | 63 +++++++++ spec/src/main/asciidoc/java-api.asciidoc | 23 +++- spec/src/main/asciidoc/overview.asciidoc | 44 +++++- .../asciidoc/protocol-wireformat.asciidoc | 58 +------- spec/src/main/asciidoc/release_notes.asciidoc | 4 +- .../health/tck/HealthReporterTest.java | 130 ++++++++++++++++++ .../tck/MultipleProceduresFailedTest.java | 2 +- 9 files changed, 324 insertions(+), 60 deletions(-) create mode 100644 api/src/main/java/org/eclipse/microprofile/health/Health.java create mode 100644 api/src/main/java/org/eclipse/microprofile/health/HealthReporter.java create mode 100644 tck/src/main/java/org/eclipse/microprofile/health/tck/HealthReporterTest.java diff --git a/api/pom.xml b/api/pom.xml index 79e2de9..f9f8584 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -39,6 +39,10 @@ org.osgi org.osgi.service.cdi + + jakarta.json + jakarta.json-api + diff --git a/api/src/main/java/org/eclipse/microprofile/health/Health.java b/api/src/main/java/org/eclipse/microprofile/health/Health.java new file mode 100644 index 0000000..cac119a --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/health/Health.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICES file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.eclipse.microprofile.health; + +import java.util.Objects; + +import jakarta.json.JsonObject; + +public class Health { + + private JsonObject payload; + + public Health(JsonObject payload) { + this.payload = payload; + } + + public JsonObject getPayload() { + return payload; + } + + public boolean isDown() { + return HealthCheckResponse.Status.DOWN.toString().equals(payload.getString("status")); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Health)) + return false; + Health health = (Health) o; + return Objects.equals(getPayload(), health.getPayload()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getPayload()); + } +} diff --git a/api/src/main/java/org/eclipse/microprofile/health/HealthReporter.java b/api/src/main/java/org/eclipse/microprofile/health/HealthReporter.java new file mode 100644 index 0000000..6c202e4 --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/health/HealthReporter.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICES file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.eclipse.microprofile.health; + +/** + * + * CDI injectable interface that allows accessing of the health information + * + * @author Martin Stefanko + * @since 4.1 + */ +public interface HealthReporter { + + /** + * The result of calling all available health checks. + * + * @return {@link Health} representing all health check procedures. + */ + Health getHealth(); + + + /** + * The result of calling all liveness health checks. + * + * @return {@link Health} representing only the liveness health check procedures. + */ + Health getLiveness(); + + + /** + * The result of calling all readiness health checks. + * + * @return {@link Health} representing only the readiness health check procedures. + */ + Health getReadiness(); + + /** + * The result of calling all startup health checks. + * + * @return {@link Health} representing only the startup health check procedures. + */ + Health getStartup(); + +} diff --git a/spec/src/main/asciidoc/java-api.asciidoc b/spec/src/main/asciidoc/java-api.asciidoc index aafa589..8cd2d47 100644 --- a/spec/src/main/asciidoc/java-api.asciidoc +++ b/spec/src/main/asciidoc/java-api.asciidoc @@ -22,9 +22,9 @@ This specification provides the following API to define health check procedures. == Common API check -The main API to provide health check procedures (readiness or liveness) on the application level is the `HealthCheck` interface: +The main API to provide health check procedures (readiness, liveness, or startup) on the application level is the `HealthCheck` interface: -``` +```java @FunctionalInterface public interface HealthCheck { @@ -112,8 +112,6 @@ public class MyCheck implements HealthCheck { } ---- - - == Constructing `HealthCheckResponse` 's Application level code is expected to use one of static methods on `HealthCheckResponse` to retrieve a `HealthCheckResponseBuilder` used to construct a response, i.e. : @@ -195,3 +193,20 @@ class MyChecks { } } ``` + +== HealthReporter + +Implementations MUST provide a CDI bean implementation of the `HealthReporter` interface that can be injected in +the provider application to query the health information of the current service instance: + +```java +@ApplicationScoped +public class MyApp { + + @Inject + private HealthReporter healthReporter; + + ... +} + +``` diff --git a/spec/src/main/asciidoc/overview.asciidoc b/spec/src/main/asciidoc/overview.asciidoc index 295a707..1da254f 100644 --- a/spec/src/main/asciidoc/overview.asciidoc +++ b/spec/src/main/asciidoc/overview.asciidoc @@ -1,5 +1,5 @@ // -// Copyright (c) 2016-2021 Eclipse Microprofile Contributors: +// Copyright (c) 2016-2025 Eclipse Microprofile Contributors: // See overview.adoc // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -66,3 +66,45 @@ The proposed solution breaks down into two parts: - Martin Stefanko - Kevin Sutter +== Guidelines + +Note that the force of these words is modified by the requirement level of the document in which they are used. + +1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that the +definition is an absolute requirement of the specification. + +2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the +definition is an absolute prohibition of the specification. + +3. SHOULD This word, or the adjective "RECOMMENDED", mean that there +may exist valid reasons in particular circumstances to ignore a +particular item, but the full implications must be understood and +carefully weighed before choosing a different course. + +4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that +there may exist valid reasons in particular circumstances when the +particular behavior is acceptable or even useful, but the full +implications should be understood and the case carefully weighed +before implementing any behavior described with this label. + +5. MAY – This word, or the adjective “OPTIONAL”, mean that an item is truly discretionary. + +== Terms used + +|=== +| Term | Description +| Producer +| The service/application that is checked + +| Consumer +| The probing end, usually a machine, that needs to verify the health information of a Producer + +| Health Check Procedure +| The code executed to determine the liveliness of a Producer + +| Producer status +| The overall status, determined by considering all health check procedure results + +| Health check procedure result +| The result of single check +|=== diff --git a/spec/src/main/asciidoc/protocol-wireformat.asciidoc b/spec/src/main/asciidoc/protocol-wireformat.asciidoc index 68b9fd2..ef1f76d 100644 --- a/spec/src/main/asciidoc/protocol-wireformat.asciidoc +++ b/spec/src/main/asciidoc/protocol-wireformat.asciidoc @@ -1,5 +1,5 @@ // -// Copyright (c) 2016-2021 Eclipse Microprofile Contributors: +// Copyright (c) 2016-2025 Eclipse Microprofile Contributors: // See overview.adoc // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,57 +20,11 @@ == Abstract This document defines the protocol to be used by components that need to ensure a compatible wireformat, agreed upon semantics and possible forms of interactions between system components that need to determine the “liveliness” or "readiness" of computing nodes in a bigger system. -=== Guidelines - -Note that the force of these words is modified by the requirement level of the document in which they are used. - -1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that the - definition is an absolute requirement of the specification. - -2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the - definition is an absolute prohibition of the specification. - -3. SHOULD This word, or the adjective "RECOMMENDED", mean that there - may exist valid reasons in particular circumstances to ignore a - particular item, but the full implications must be understood and - carefully weighed before choosing a different course. - -4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that - there may exist valid reasons in particular circumstances when the - particular behavior is acceptable or even useful, but the full - implications should be understood and the case carefully weighed - before implementing any behavior described with this label. - - -5. MAY – This word, or the adjective “OPTIONAL”, mean that an item is truly discretionary. - - == Goals * MUST be compatibility with well known cloud platforms (i.e. http://kubernetes.io/docs/user-guide/liveness/) * MUST be appropriate for machine-to-machine communication * SHOULD give enough information for a human administrator - -== Terms used - -|=== -| Term | Description -| Producer -| The service/application that is checked - -| Consumer -| The probing end, usually a machine, that needs to verify the liveness or readiness of a Producer - -| Health Check Procedure -| The code executed to determine the liveliness of a Producer - -| Producer status -| The overall status, determined by considering all health check procedure results - -| Health check procedure result -| The result of single check -|=== - == Protocol Overview 1. Consumer invokes the health check of a Producer through any of the supported protocols @@ -253,7 +207,7 @@ The following table gives valid health check responses for all kinds of health c |=== | Request | HTTP Status | JSON Payload | Status | Comment -| /health/live +a| /health/live /health/ready /health/started /health @@ -262,7 +216,7 @@ The following table gives valid health check responses for all kinds of health c | UP | Check with payload. See <>. -| /health/live +a| /health/live /health/ready /health/started /health @@ -271,7 +225,7 @@ The following table gives valid health check responses for all kinds of health c | UP | Check with no procedures expected or installed. See <> -| /health/live +a| /health/live /health/ready /health/started /health @@ -280,7 +234,7 @@ The following table gives valid health check responses for all kinds of health c | Down | Check failed -| /health/live +a| /health/live /health/ready /health/started /health @@ -289,7 +243,7 @@ The following table gives valid health check responses for all kinds of health c | Down | Check with procedures expected but not yet installed. See <> -| /health/live +a| /health/live /health/ready /health/started /health diff --git a/spec/src/main/asciidoc/release_notes.asciidoc b/spec/src/main/asciidoc/release_notes.asciidoc index 713ea00..3bfb439 100644 --- a/spec/src/main/asciidoc/release_notes.asciidoc +++ b/spec/src/main/asciidoc/release_notes.asciidoc @@ -27,7 +27,7 @@ This section documents the changes introduced by individual releases. The following changes occurred in the 4.1 release, compared to 4.0. -A full list of changes may be found on the link:https://github.com/eclipse/microprofile-health/milestone/8[Future] +A full list of changes may be found on the link:https://github.com/microprofile/microprofile-health/milestone/10[`4.1 milestone`]. ==== Incompatible Changes @@ -47,7 +47,7 @@ signature will be refactored accordingly in the next MicroProfile Health major r ==== Functional Changes -- None +- Introduction of the link:../../../../api/src/main/java/org/eclipse/microprofile/health/HealthReporter.java[`HealthReporter`] interface. ==== Other Changes diff --git a/tck/src/main/java/org/eclipse/microprofile/health/tck/HealthReporterTest.java b/tck/src/main/java/org/eclipse/microprofile/health/tck/HealthReporterTest.java new file mode 100644 index 0000000..b3077e5 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/health/tck/HealthReporterTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICES file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.eclipse.microprofile.health.tck; + +import static org.eclipse.microprofile.health.tck.DeploymentUtils.createWarFileWithClasses; +import static org.testng.Assert.assertFalse; + +import java.util.function.Supplier; + +import org.eclipse.microprofile.health.Health; +import org.eclipse.microprofile.health.HealthReporter; +import org.eclipse.microprofile.health.tck.deployment.SuccessfulLiveness; +import org.eclipse.microprofile.health.tck.deployment.SuccessfulReadiness; +import org.eclipse.microprofile.health.tck.deployment.SuccessfulStartup; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.shrinkwrap.api.Archive; +import org.testng.Assert; +import org.testng.annotations.Test; + +import jakarta.inject.Inject; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; + +/** + * Verifies the implementation of {@link org.eclipse.microprofile.health.HealthReporter} outputs. + * + * @author Martin Stefanko + */ +public class HealthReporterTest extends TCKBase { + + @Inject + private HealthReporter reporter; + + @Deployment + public static Archive getDeployment() { + return createWarFileWithClasses(HealthReporterTest.class.getSimpleName(), SuccessfulLiveness.class, + SuccessfulReadiness.class, SuccessfulStartup.class); + } + + /** + * Verifies default health (all procedures). + */ + @Test + public void testHealth() { + Health health = reporter.getHealth(); + + // status code + assertFalse(health.isDown()); + + JsonObject json = health.getPayload(); + + // response size + JsonArray checks = json.getJsonArray("checks"); + Assert.assertEquals(checks.size(), 3, "Expected three check responses"); + + // verify that all 3 procedures are present + for (JsonObject check : checks.getValuesAs(JsonObject.class)) { + String id = check.getString("name"); + if (id.equals("successful-check")) { + verifySuccessStatus(check); + } else { + Assert.fail("Unexpected response payload structure"); + } + } + + assertOverallSuccess(json); + } + + /** + * Verifies liveness health procedures. + */ + @Test + public void testLiveness() { + verifySingleResponse(() -> reporter.getLiveness()); + } + + /** + * Verifies readiness health procedures. + */ + @Test + public void testReadiness() { + verifySingleResponse(() -> reporter.getReadiness()); + } + + /** + * Verifies startup health procedures. + */ + @Test + public void testStartup() { + verifySingleResponse(() -> reporter.getStartup()); + } + + private void verifySingleResponse(Supplier healthSupplier) { + Health health = healthSupplier.get(); + + // status code + assertFalse(health.isDown()); + + JsonObject json = health.getPayload(); + + // response size + JsonArray checks = json.getJsonArray("checks"); + Assert.assertEquals(checks.size(), 1, "Expected a single check response"); + + // single procedure response + assertSuccessfulCheck(checks.getJsonObject(0), "successful-check"); + + assertOverallSuccess(json); + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/health/tck/MultipleProceduresFailedTest.java b/tck/src/main/java/org/eclipse/microprofile/health/tck/MultipleProceduresFailedTest.java index 98c6ffd..ca3f4e7 100644 --- a/tck/src/main/java/org/eclipse/microprofile/health/tck/MultipleProceduresFailedTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/health/tck/MultipleProceduresFailedTest.java @@ -63,7 +63,7 @@ public void testFailureResponsePayload() { // response size JsonArray checks = json.getJsonArray("checks"); - Assert.assertEquals(checks.size(), 3, "Expected four check responses"); + Assert.assertEquals(checks.size(), 3, "Expected three check responses"); // verify that all 3 procedures are present for (JsonObject check : checks.getValuesAs(JsonObject.class)) {