Skip to content

Commit

Permalink
Unblock SmallRye Health exposed routes
Browse files Browse the repository at this point in the history
  • Loading branch information
xstefank committed Jan 9, 2024
1 parent 02471b8 commit 85fdcb8
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 55 deletions.
5 changes: 5 additions & 0 deletions extensions/smallrye-health/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -217,7 +216,6 @@ public void defineHealthRoutes(BuildProducer<RouteBuildItem> routes,
.routeConfigKey("quarkus.smallrye-health.root-path")
.handler(new SmallRyeHealthHandler())
.displayOnNotFoundPage()
.blockingRoute()
.build());

// Register the liveness handler
Expand All @@ -226,7 +224,6 @@ public void defineHealthRoutes(BuildProducer<RouteBuildItem> routes,
.nestedRoute(healthConfig.rootPath, healthConfig.livenessPath)
.handler(new SmallRyeLivenessHandler())
.displayOnNotFoundPage()
.blockingRoute()
.build());

// Register the readiness handler
Expand All @@ -235,29 +232,14 @@ public void defineHealthRoutes(BuildProducer<RouteBuildItem> routes,
.nestedRoute(healthConfig.rootPath, healthConfig.readinessPath)
.handler(new SmallRyeReadinessHandler())
.displayOnNotFoundPage()
.blockingRoute()
.build());

// 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(nonApplicationRootPathBuildItem.routeBuilder()
.management("quarkus.smallrye-health.management.enabled")
.nestedRoute(healthConfig.rootPath, healthConfig.groupPath)
.handler(new SmallRyeHealthGroupHandler())
.displayOnNotFoundPage()
.blockingRoute()
.build());

SmallRyeIndividualHealthGroupHandler handler = new SmallRyeIndividualHealthGroupHandler();
Expand All @@ -266,7 +248,6 @@ public void defineHealthRoutes(BuildProducer<RouteBuildItem> routes,
.nestedRoute(healthConfig.rootPath, healthConfig.groupPath + "/*")
.handler(handler)
.displayOnNotFoundPage()
.blockingRoute()
.build());

// Register the wellness handler
Expand All @@ -275,7 +256,6 @@ public void defineHealthRoutes(BuildProducer<RouteBuildItem> routes,
.nestedRoute(healthConfig.rootPath, healthConfig.wellnessPath)
.handler(new SmallRyeWellnessHandler())
.displayOnNotFoundPage()
.blockingRoute()
.build());

// Register the startup handler
Expand All @@ -284,7 +264,6 @@ public void defineHealthRoutes(BuildProducer<RouteBuildItem> routes,
.nestedRoute(healthConfig.rootPath, healthConfig.startupPath)
.handler(new SmallRyeStartupHandler())
.displayOnNotFoundPage()
.blockingRoute()
.build());

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package io.quarkus.smallrye.health.test;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

import java.util.logging.Level;
import java.util.logging.LogRecord;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.scheduler.Scheduled;
import io.quarkus.test.InMemoryLogHandler;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.restassured.parsing.Parser;
import io.smallrye.health.SmallRyeHealthReporter;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlockingNonBlockingTest {

private static final java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager()
.getLogger("io.vertx.core");
private static final InMemoryLogHandler inMemoryLogHandler = new InMemoryLogHandler(
record -> record.getLevel().intValue() >= java.util.logging.Level.WARNING.intValue());

@BeforeEach
public void setLogHandler() {
inMemoryLogHandler.getRecords().clear();
rootLogger.addHandler(inMemoryLogHandler);
}

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(BlockingHealthCheck.class, SchedulerBean.class)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"));

@Test
public void testRegisterHealthOnBlockingThreadStep1() {
// wait for the initial scheduler call to finish
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

try {
RestAssured.defaultParser = Parser.JSON;
// repeat the call a few times since the block isn't always logged
for (int i = 0; i < 3; i++) {
RestAssured.when().get("/q/health").then()
.body("status", is("UP"),
"checks.status", contains("UP"),
"checks.name", contains("blocking"));
}
} finally {
RestAssured.reset();
}

if (!inMemoryLogHandler.getRecords().isEmpty()) {
LogRecord logRecord = inMemoryLogHandler.getRecords().get(0);
assertEquals(Level.WARNING, logRecord.getLevel());
assertFalse(logRecord.getMessage().contains("has been blocked for"),
"The blocking health check ran on eventloop thread");
}
}

@Liveness
static final class BlockingHealthCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
// block for 3s which is more than allowed default blocking duration of eventloop (2s)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return HealthCheckResponse.up("blocking");
}
}

@ApplicationScoped
static final class SchedulerBean {

@Inject
SmallRyeHealthReporter smallRyeHealthReporter;

@Scheduled(every = "20s")
public void registerHealth() {
smallRyeHealthReporter.getHealth();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;

import io.quarkus.runtime.BlockingOperationControl;
import io.smallrye.health.AsyncHealthCheckFactory;
import io.smallrye.health.api.AsyncHealthCheck;
import io.smallrye.mutiny.Uni;
Expand All @@ -28,14 +27,12 @@ public QuarkusAsyncHealthCheckFactory(Vertx vertx) {
@Override
public Uni<HealthCheckResponse> callSync(HealthCheck healthCheck) {
Uni<HealthCheckResponse> healthCheckResponseUni = super.callSync(healthCheck);
return BlockingOperationControl.isBlockingAllowed() ? healthCheckResponseUni
: healthCheckResponseUni.runSubscriptionOn(MutinyHelper.blockingExecutor(vertx, false));
return healthCheckResponseUni.runSubscriptionOn(MutinyHelper.blockingExecutor(vertx, false));
}

@Override
public Uni<HealthCheckResponse> callAsync(AsyncHealthCheck asyncHealthCheck) {
Uni<HealthCheckResponse> healthCheckResponseUni = super.callAsync(asyncHealthCheck);
return !BlockingOperationControl.isBlockingAllowed() ? healthCheckResponseUni
: healthCheckResponseUni.runSubscriptionOn(MutinyHelper.executor(vertx));
return healthCheckResponseUni.runSubscriptionOn(MutinyHelper.executor(vertx));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeHealthGroupHandler extends SmallRyeHealthHandlerBase {

@Override
protected SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getHealthGroups();
protected Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getHealthGroupsAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeHealthHandler extends SmallRyeHealthHandlerBase {

@Override
protected SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getHealth();
protected Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getHealthAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser;
import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.vertx.MutinyHelper;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;

abstract class SmallRyeHealthHandlerBase implements Handler<RoutingContext> {

protected abstract SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext routingContext);
protected abstract Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext routingContext);

@Override
public void handle(RoutingContext ctx) {
Expand All @@ -41,19 +45,21 @@ private void doHandle(RoutingContext ctx) {
Arc.container().instance(CurrentIdentityAssociation.class).get().setIdentity(user.getSecurityIdentity());
}
SmallRyeHealthReporter reporter = Arc.container().instance(SmallRyeHealthReporter.class).get();
SmallRyeHealth health = getHealth(reporter, ctx);
HttpServerResponse resp = ctx.response();
if (health.isDown()) {
resp.setStatusCode(503);
}
resp.headers().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
Buffer buffer = Buffer.buffer(256); // this size seems to cover the basic health checks
try (BufferOutputStream outputStream = new BufferOutputStream(buffer);) {
reporter.reportHealth(outputStream, health);
resp.end(buffer);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Context context = Vertx.currentContext();
getHealth(reporter, ctx).emitOn(MutinyHelper.executor(context))
.subscribe().with(health -> {
HttpServerResponse resp = ctx.response();
if (health.isDown()) {
resp.setStatusCode(503);
}
resp.headers().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
Buffer buffer = Buffer.buffer(256); // this size seems to cover the basic health checks
try (BufferOutputStream outputStream = new BufferOutputStream(buffer);) {
reporter.reportHealth(outputStream, health);
resp.end(buffer);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeIndividualHealthGroupHandler extends SmallRyeHealthHandlerBase {

@Override
protected SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
protected Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
String group = ctx.normalizedPath().substring(ctx.normalizedPath().lastIndexOf("/") + 1);
return reporter.getHealthGroup(group);
return reporter.getHealthGroupAsync(group);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeLivenessHandler extends SmallRyeHealthHandlerBase {

@Override
protected SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getLiveness();
protected Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getLivenessAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeReadinessHandler extends SmallRyeHealthHandlerBase {

@Override
protected SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext routingContext) {
return reporter.getReadiness();
protected Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getReadinessAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeStartupHandler extends SmallRyeHealthHandlerBase {

@Override
protected SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext routingContext) {
return reporter.getStartup();
protected Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getStartupAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.smallrye.health.SmallRyeHealth;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeWellnessHandler extends SmallRyeHealthHandlerBase {

@Override
protected SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext routingContext) {
return reporter.getWellness();
protected Uni<SmallRyeHealth> getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) {
return reporter.getWellnessAsync();
}
}

0 comments on commit 85fdcb8

Please sign in to comment.