Skip to content

Commit

Permalink
Update to SmallRye Health 2.2.0
Browse files Browse the repository at this point in the history
Add @healthGroup and @HealthGroups support
  • Loading branch information
kenfinnigan authored and antoinesd committed Mar 20, 2020
1 parent f75581c commit 65c2138
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -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.
*/
Expand All @@ -56,22 +68,28 @@ class SmallRyeHealthProcessor {
@ConfigRoot(name = "smallrye-health")
static final class SmallRyeHealthConfig {
/**
* Root path for health-checking servlets.
* Root path for health-checking endpoints.
*/
@ConfigItem(defaultValue = "/health")
String rootPath;

/**
* The relative path of the liveness health-checking servlet.
* The relative path of the liveness health-checking endpoint.
*/
@ConfigItem(defaultValue = "/live")
String livenessPath;

/**
* The relative path of the readiness health-checking servlet.
* The relative path of the readiness health-checking endpoint.
*/
@ConfigItem(defaultValue = "/ready")
String readinessPath;

/**
* The relative path of the health group endpoint.
*/
@ConfigItem(defaultValue = "/group")
String groupPath;
}

@BuildStep
Expand All @@ -94,29 +112,20 @@ void healthCheck(BuildProducer<AdditionalBeanBuildItem> buildItemBuildProducer,
@SuppressWarnings("unchecked")
void build(SmallRyeHealthRecorder recorder, RecorderContext recorderContext,
BuildProducer<FeatureBuildItem> feature,
BuildProducer<RouteBuildItem> routes,
BuildProducer<AdditionalBeanBuildItem> additionalBean,
BuildProducer<BeanDefiningAnnotationBuildItem> beanDefiningAnnotation,
BuildProducer<NotFoundPageDisplayableEndpointBuildItem> displayableEndpoints,
LaunchModeBuildItem launchModeBuildItem) throws IOException {

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
Expand All @@ -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<String> providers = ServiceUtil.classNamesNamedIn(getClass().getClassLoader(),
"META-INF/services/" + HealthCheckResponseProvider.class.getName());
Expand All @@ -146,6 +164,50 @@ void build(SmallRyeHealthRecorder recorder, RecorderContext recorderContext,
(Class<? extends HealthCheckResponseProvider>) recorderContext.classProxy(providers.iterator().next()));
}

@BuildStep
public void defineHealthRoutes(BuildProducer<RouteBuildItem> 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<String> 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<KubernetesHealthLivenessPathBuildItem> livenessPathItemProducer,
Expand Down
10 changes: 0 additions & 10 deletions extensions/smallrye-health/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-health</artifactId>
<exclusions>
<exclusion>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RoutingContext> {

@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();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<RoutingContext> {

@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();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down

0 comments on commit 65c2138

Please sign in to comment.