From 537f7b1193ca438a495a1adde4b10fca4930f141 Mon Sep 17 00:00:00 2001 From: Ken Finnigan Date: Thu, 5 Mar 2020 16:22:31 -0500 Subject: [PATCH] Update to SmallRye Health 2.2.0 Add @HealthGroup and @HealthGroups support --- .../deployment/SmallRyeHealthProcessor.java | 82 ++++++++++++++++--- extensions/smallrye-health/runtime/pom.xml | 6 -- .../runtime/SmallRyeHealthGroupHandler.java | 44 ++++++++++ .../SmallRyeIndividualHealthGroupHandler.java | 45 ++++++++++ .../SimpleCombinedHealthGroupCheck.java | 15 ++++ .../it/health/SimpleHealthGroupCheck.java | 14 ++++ .../io/quarkus/it/main/HealthTestCase.java | 21 +++++ 7 files changed, 211 insertions(+), 16 deletions(-) create mode 100644 extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthGroupHandler.java create mode 100644 extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeIndividualHealthGroupHandler.java create mode 100644 integration-tests/main/src/main/java/io/quarkus/it/health/SimpleCombinedHealthGroupCheck.java create mode 100644 integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthGroupCheck.java diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java index 985cb2db255d3..e6cfd468115bb 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java @@ -1,6 +1,7 @@ package io.quarkus.smallrye.health.deployment; import java.io.IOException; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -9,9 +10,12 @@ import org.eclipse.microprofile.health.Liveness; import org.eclipse.microprofile.health.Readiness; import org.eclipse.microprofile.health.spi.HealthCheckResponseProvider; +import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -28,14 +32,18 @@ import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; import io.quarkus.smallrye.health.runtime.ShutdownReadinessListener; +import io.quarkus.smallrye.health.runtime.SmallRyeHealthGroupHandler; import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler; import io.quarkus.smallrye.health.runtime.SmallRyeHealthRecorder; +import io.quarkus.smallrye.health.runtime.SmallRyeIndividualHealthGroupHandler; import io.quarkus.smallrye.health.runtime.SmallRyeLivenessHandler; import io.quarkus.smallrye.health.runtime.SmallRyeReadinessHandler; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; import io.quarkus.vertx.http.runtime.HandlerType; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; +import io.smallrye.health.HealthGroup; +import io.smallrye.health.HealthGroups; import io.smallrye.health.SmallRyeHealthReporter; class SmallRyeHealthProcessor { @@ -46,6 +54,10 @@ class SmallRyeHealthProcessor { private static final DotName READINESS = DotName.createSimple(Readiness.class.getName()); + private static final DotName HEALTH_GROUP = DotName.createSimple(HealthGroup.class.getName()); + + private static final DotName HEALTH_GROUPS = DotName.createSimple(HealthGroups.class.getName()); + /** * The configuration for health checking. */ @@ -72,6 +84,12 @@ static final class SmallRyeHealthConfig { */ @ConfigItem(defaultValue = "/ready") String readinessPath; + + /** + * The relative path of the health group servlet. + */ + @ConfigItem(defaultValue = "/group") + String groupPath; } @BuildStep @@ -94,7 +112,6 @@ void healthCheck(BuildProducer buildItemBuildProducer, @SuppressWarnings("unchecked") void build(SmallRyeHealthRecorder recorder, RecorderContext recorderContext, BuildProducer feature, - BuildProducer routes, BuildProducer additionalBean, BuildProducer beanDefiningAnnotation, BuildProducer displayableEndpoints, @@ -102,21 +119,13 @@ void build(SmallRyeHealthRecorder recorder, RecorderContext recorderContext, feature.produce(new FeatureBuildItem(FeatureBuildItem.SMALLRYE_HEALTH)); - // Register the health handler - routes.produce(new RouteBuildItem(health.rootPath, new SmallRyeHealthHandler(), HandlerType.BLOCKING)); - routes.produce( - new RouteBuildItem(health.rootPath + health.livenessPath, new SmallRyeLivenessHandler(), - HandlerType.BLOCKING)); - routes.produce( - new RouteBuildItem(health.rootPath + health.readinessPath, new SmallRyeReadinessHandler(), - HandlerType.BLOCKING)); - // add health endpoints to not found page if (launchModeBuildItem.getLaunchMode().isDevOrTest()) { displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath)); displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath + health.livenessPath)); displayableEndpoints .produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath + health.readinessPath)); + displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath + health.groupPath)); } // Make ArC discover the beans marked with the @Health qualifier @@ -128,9 +137,18 @@ void build(SmallRyeHealthRecorder recorder, RecorderContext recorderContext, // Make ArC discover the beans marked with the @Readiness qualifier beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(READINESS)); + // Make ArC discover the beans marked with the @HealthGroup qualifier + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(HEALTH_GROUP)); + + // Make ArC discover the beans marked with the repeatable @HealthGroups annotation + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(HEALTH_GROUPS)); + // Add additional beans additionalBean.produce(new AdditionalBeanBuildItem(SmallRyeHealthReporter.class)); + // Make ArC discover @HealthGroup as a qualifier + additionalBean.produce(new AdditionalBeanBuildItem(HealthGroup.class)); + // Discover and register the HealthCheckResponseProvider Set providers = ServiceUtil.classNamesNamedIn(getClass().getClassLoader(), "META-INF/services/" + HealthCheckResponseProvider.class.getName()); @@ -146,6 +164,50 @@ void build(SmallRyeHealthRecorder recorder, RecorderContext recorderContext, (Class) recorderContext.classProxy(providers.iterator().next())); } + @BuildStep + public void defineHealthRoutes(BuildProducer routes, + BeanArchiveIndexBuildItem beanArchiveIndex) { + IndexView index = beanArchiveIndex.getIndex(); + + // Register the health handler + routes.produce(new RouteBuildItem(health.rootPath, new SmallRyeHealthHandler(), HandlerType.BLOCKING)); + + // Register the liveness handler + routes.produce( + new RouteBuildItem(health.rootPath + health.livenessPath, new SmallRyeLivenessHandler(), + HandlerType.BLOCKING)); + + // Register the readiness handler + routes.produce( + new RouteBuildItem(health.rootPath + health.readinessPath, new SmallRyeReadinessHandler(), + HandlerType.BLOCKING)); + + // Find all health groups + Set healthGroups = new HashSet<>(); + // with simple @HealthGroup annotations + for (AnnotationInstance healthGroupAnnotation : index.getAnnotations(HEALTH_GROUP)) { + healthGroups.add(healthGroupAnnotation.value().asString()); + } + // with @HealthGroups repeatable annotations + for (AnnotationInstance healthGroupsAnnotation : index.getAnnotations(HEALTH_GROUPS)) { + for (AnnotationInstance healthGroupAnnotation : healthGroupsAnnotation.value().asNestedArray()) { + healthGroups.add(healthGroupAnnotation.value().asString()); + } + } + + // Register the health group handlers + routes.produce( + new RouteBuildItem(health.rootPath + health.groupPath, new SmallRyeHealthGroupHandler(), + HandlerType.BLOCKING)); + + SmallRyeIndividualHealthGroupHandler handler = new SmallRyeIndividualHealthGroupHandler(); + for (String healthGroup : healthGroups) { + routes.produce( + new RouteBuildItem(health.rootPath + health.groupPath + "/" + healthGroup, + handler, HandlerType.BLOCKING)); + } + } + @BuildStep public void kubernetes(HttpBuildTimeConfig httpConfig, BuildProducer livenessPathItemProducer, diff --git a/extensions/smallrye-health/runtime/pom.xml b/extensions/smallrye-health/runtime/pom.xml index f340bc2a7a61d..2188870cd8c48 100644 --- a/extensions/smallrye-health/runtime/pom.xml +++ b/extensions/smallrye-health/runtime/pom.xml @@ -18,12 +18,6 @@ io.smallrye smallrye-health - - - javax.inject - javax.inject - - jakarta.inject diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthGroupHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthGroupHandler.java new file mode 100644 index 0000000000000..8a56c8f77b078 --- /dev/null +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthGroupHandler.java @@ -0,0 +1,44 @@ +package io.quarkus.smallrye.health.runtime; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; + +import javax.enterprise.inject.spi.CDI; + +import io.quarkus.arc.Arc; +import io.smallrye.health.SmallRyeHealth; +import io.smallrye.health.SmallRyeHealthReporter; +import io.vertx.core.Handler; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; + +public class SmallRyeHealthGroupHandler implements Handler { + + @Override + public void handle(RoutingContext event) { + boolean activated = RequestScopeHelper.activeRequestScope(); + + try { + SmallRyeHealthReporter reporter = CDI.current().select(SmallRyeHealthReporter.class).get(); + SmallRyeHealth health = reporter.getHealthGroups(); + HttpServerResponse resp = event.response(); + if (health.isDown()) { + resp.setStatusCode(503); + } + resp.headers().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8"); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + reporter.reportHealth(outputStream, health); + resp.end(Buffer.buffer(outputStream.toByteArray())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } finally { + if (activated) { + Arc.container().requestContext().terminate(); + } + } + } +} diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeIndividualHealthGroupHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeIndividualHealthGroupHandler.java new file mode 100644 index 0000000000000..f98523674fc6b --- /dev/null +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeIndividualHealthGroupHandler.java @@ -0,0 +1,45 @@ +package io.quarkus.smallrye.health.runtime; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; + +import javax.enterprise.inject.spi.CDI; + +import io.quarkus.arc.Arc; +import io.smallrye.health.SmallRyeHealth; +import io.smallrye.health.SmallRyeHealthReporter; +import io.vertx.core.Handler; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; + +public class SmallRyeIndividualHealthGroupHandler implements Handler { + + @Override + public void handle(RoutingContext event) { + boolean activated = RequestScopeHelper.activeRequestScope(); + + try { + SmallRyeHealthReporter reporter = CDI.current().select(SmallRyeHealthReporter.class).get(); + String group = event.normalisedPath().substring(event.normalisedPath().lastIndexOf("/") + 1); + SmallRyeHealth health = reporter.getHealthGroup(group); + HttpServerResponse resp = event.response(); + if (health.isDown()) { + resp.setStatusCode(503); + } + resp.headers().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8"); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + reporter.reportHealth(outputStream, health); + resp.end(Buffer.buffer(outputStream.toByteArray())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } finally { + if (activated) { + Arc.container().requestContext().terminate(); + } + } + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleCombinedHealthGroupCheck.java b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleCombinedHealthGroupCheck.java new file mode 100644 index 0000000000000..c129420d00199 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleCombinedHealthGroupCheck.java @@ -0,0 +1,15 @@ +package io.quarkus.it.health; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; + +import io.smallrye.health.HealthGroup; + +@HealthGroup("group1") +@HealthGroup("group2") +public class SimpleCombinedHealthGroupCheck implements HealthCheck { + @Override + public HealthCheckResponse call() { + return HealthCheckResponse.up("combined"); + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthGroupCheck.java b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthGroupCheck.java new file mode 100644 index 0000000000000..ef97a4f8b2fff --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthGroupCheck.java @@ -0,0 +1,14 @@ +package io.quarkus.it.health; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; + +import io.smallrye.health.HealthGroup; + +@HealthGroup("group1") +public class SimpleHealthGroupCheck implements HealthCheck { + @Override + public HealthCheckResponse call() { + return HealthCheckResponse.up("single"); + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java index f7d18d6c59c22..7cf6b2a9989fd 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java @@ -29,6 +29,27 @@ public void testHealthCheck() { .body("status", is("UP"), "checks.status", containsInAnyOrder("UP"), "checks.name", containsInAnyOrder("Database connections health check")); + + RestAssured.when().get("/health/group/group1").then() + .contentType(ContentType.JSON) + .header("Content-Type", containsString("charset=UTF-8")) + .body("status", is("UP"), + "checks.status", containsInAnyOrder("UP", "UP"), + "checks.name", containsInAnyOrder("single", "combined")); + + RestAssured.when().get("/health/group/group2").then() + .contentType(ContentType.JSON) + .header("Content-Type", containsString("charset=UTF-8")) + .body("status", is("UP"), + "checks.status", containsInAnyOrder("UP"), + "checks.name", containsInAnyOrder("combined")); + + RestAssured.when().get("/health/group").then() + .contentType(ContentType.JSON) + .header("Content-Type", containsString("charset=UTF-8")) + .body("status", is("UP"), + "checks.status", containsInAnyOrder("UP", "UP"), + "checks.name", containsInAnyOrder("single", "combined")); } finally { RestAssured.reset(); }